# Deterministic memories


The Trading agent we deployed earlier will automatically have session persistence when deployed on Agent Platform. You can test this by asking it a day later “What trades did you execute” from the same session that you executed the trades from. The full conversation history is preserved, even while the agent is suspended (you’re not paying for it to run continuously).


You can also inspect the saved session data from the console.

[https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/memory-bank/setup](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/memory-bank/setup)

But if you start a new session, and ask the agent what it did _yesterday_, it won’t know.

```
> what did you trade yesterday?

I am a trading agent designed to execute trades based on the current news cycle. I do not have a memory of past trading days. Would you like me to run today's trading cycle?
```

In the [previous post](/ap/2-memories/2.1-add-memories/) we gave the agent a memory by calling `add_session_to_memory()` in an after-agent callback. That works, but it leans on an embedding model to decide what’s worth keeping from each session — which makes it a little non-deterministic. Say “remember that …” and it almost certainly will; let it summarize a trading run on its own, and the specific trades may or may not survive.



For a trading agent, the trades are exactly the thing we want to remember, every single time. So rather than hope the model picks them out, let’s write that memory ourselves.

There are two pieces to saving a memory deterministically.

First, capture what happened — in a structured way, at the moment it happens. The trade is a tool call, so `place_trade_order` is the natural place to record it: we stash each confirmed fill in the tool context’s session state as the order goes through.

```python {hl_lines=[12]}
        # Record the confirmed fill in session state so save_to_memory can
        # persist a clean, structured trade record at the end of the cycle.
        if tool_context is not None:
            trades = tool_context.state.get("trades_this_session", [])
            trades.append({
                "symbol": symbol,
                "side": side,
                "amount_usd": amount_usd,
                "qty": qty,
                "reason": reason,
            })
            tool_context.state["trades_this_session"] = trades
```

Second, when the session’s memory save fires, do the embedding-based review *and* write our own memory for the trades. The full transcript still goes to memory for fuzzy cross-session recall — news context, the agent’s reasoning — but on top of that we build a clean, structured record from the fills we captured and save it directly with `add_memory()`. There’s no LLM in that second path, so the trade record lands every time.

```python {hl_lines=[10, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]}
async def save_to_memory(callback_context: CallbackContext):
    # Full transcript: general cross-session recall (news context, reasoning).
    await callback_context.add_events_to_memory(
        events=callback_context.session.events,
        custom_metadata={"force_flush": True},
    )

    # Structured trade record: a clean, guaranteed memory of exactly what was
    # traded and why, built from the orders captured in place_trade_order.
    trades = callback_context.state.get("trades_this_session", [])
    if not trades:
        return

    lines = []
    for t in trades:
        size = f"${t['amount_usd']}" if t.get("amount_usd") else f"{t['qty']} shares"
        rationale = f" — {t['reason']}" if t.get("reason") else ""
        lines.append(f"{t['side'].upper()} {t['symbol']} {size}{rationale}")
    text = "Trades executed this session:\n" + "\n".join(lines)
    print(text, flush=True)

    await callback_context.add_memory(
        memories=[MemoryEntry(
            content=types.Content(role="model", parts=[types.Part(text=text)]),
            author="TradingAgent",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )],
        # Set a TTL of ~1 month (30 days) so the trading history can be recalled
        # for a month while avoiding too much clutter.
        custom_metadata={"type": "trade", "force_flush": True, "ttl": "2592000s"},
    )
```

A couple of other details in the diff are worth a mention. We’ve swapped last post’s `add_session_to_memory()` for `add_events_to_memory()`, which lets us pass the events explicitly and force a flush — but under the hood it’s still the same embedding-based extraction, just handling the fuzzy half. And `place_trade_order` gains a `reason` argument, which the agent’s instructions now tell it to always pass: the ticker, its sentiment score, and a one-line news summary. That rationale rides along into the structured memory, so a future session can recall not just *what* we traded, but *why*.

You’ll notice that memory also carries a `ttl` of `"2592000s"` — about a month — and that’s the one place the two kinds of memory really part ways. The embedding-based transcript is a fuzzy, general recollection: the model only pulls it back when it happens to be relevant, and a vague sense of “we’ve looked at chip stocks before” doesn’t really go stale, so it’s fine to let it sit. The structured trade records are the opposite — precise, written on *every* run, and genuinely misleading once they age. A month-old “BUY NVDA $1000” tells the agent nothing useful about today’s market, but it’ll still get retrieved and reasoned over as though it did. Left alone they’d also stack up fast — one per trade, every trading day — which is more to store and more noise to search through. So we give the deterministic memories an expiry the fuzzy ones don’t need: recent trades inform the next session, and anything older quietly ages out. The `ttl` only rides along on the `add_memory()` call, so the transcript half is untouched. (Your real audit trail lives in Alpaca, not here — Memory Bank is the agent’s working memory, not your system of record.) Tune the window to however long a trade stays relevant to your strategy; a month is a sensible default for a news-driven one like this.

The full diff to add this is as follows.

<details class="expander">
  <summary class="expander-summary">agent.py.diff</summary>
  <div class="expander-content">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>$ diff -u ../02_Memories/trading_agent/agent.py trading_agent/agent.py 
</span></span><span style="display:flex;"><span><span style="color:#f92672">--- ../02_Memories/trading_agent/agent.py       2026-05-18 04:50:36.609821500 +0000
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+++ trading_agent/agent.py      2026-06-24 00:33:46.870713112 +0000
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -1,7 +1,7 @@
</span></span></span><span style="display:flex;"><span> import os
</span></span><span style="display:flex;"><span> import sys
</span></span><span style="display:flex;"><span> import time
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from datetime import datetime, timedelta
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from datetime import datetime, timedelta, timezone
</span></span></span><span style="display:flex;"><span> from typing import Dict, Any, Optional
</span></span><span style="display:flex;"><span> from dotenv import load_dotenv
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -13,8 +13,11 @@
</span></span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span> from google.adk.agents import Agent
</span></span><span style="display:flex;"><span> from google.adk.agents.callback_context import CallbackContext
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from google.adk.memory.memory_entry import MemoryEntry
</span></span></span><span style="display:flex;"><span> from google.adk.tools.load_memory_tool import LoadMemoryTool
</span></span><span style="display:flex;"><span> from google.adk.tools.preload_memory_tool import PreloadMemoryTool
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from google.adk.tools.tool_context import ToolContext
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from google.genai import types
</span></span></span><span style="display:flex;"><span> from vertexai import agent_engines
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span> # Load environment variables
</span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -106,14 +109,23 @@
</span></span></span><span style="display:flex;"><span>     quote = stock_client.get_stock_latest_quote(request_params)
</span></span><span style="display:flex;"><span>     return float(quote[symbol].ask_price)
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span><span style="color:#f92672">-def place_trade_order(symbol: str, side: str, amount_usd: Optional[float] = None, qty: Optional[float] = None) -&gt; str:
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;&#34;&#34;Places a market order (fractional shares supported for buys).&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+def place_trade_order(symbol: str, side: str, amount_usd: Optional[float] = None, qty: Optional[float] = None, reason: str = &#34;&#34;, tool_context: ToolContext = None) -&gt; str:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;&#34;&#34;Places a market order (fractional shares supported for buys).
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    Args:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        symbol: The ticker to trade.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        side: &#39;buy&#39; or &#39;sell&#39;.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        amount_usd: Dollar amount for a notional buy.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        qty: Share quantity (required for sells).
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        reason: Short rationale for the trade (ticker, sentiment score, news summary).
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                Saved to memory so future sessions can recall why this trade was made.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span>     side = side.lower()
</span></span><span style="display:flex;"><span>     if side not in [&#39;buy&#39;, &#39;sell&#39;]:
</span></span><span style="display:flex;"><span>         return &#34;Error: Side must be &#39;buy&#39; or &#39;sell&#39;.&#34;
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>     order_side = OrderSide.BUY if side == &#39;buy&#39; else OrderSide.SELL
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span>     try:
</span></span><span style="display:flex;"><span>         if amount_usd is not None and side == &#39;buy&#39;:
</span></span><span style="display:flex;"><span>             order_request = MarketOrderRequest(
</span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -134,16 +146,56 @@
</span></span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>         time.sleep(1)
</span></span><span style="display:flex;"><span>         order = trading_client.submit_order(order_data=order_request)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        # Record the confirmed fill in session state so save_to_memory can
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        # persist a clean, structured trade record at the end of the cycle.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        if tool_context is not None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            trades = tool_context.state.get(&#34;trades_this_session&#34;, [])
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            trades.append({
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                &#34;symbol&#34;: symbol,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                &#34;side&#34;: side,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                &#34;amount_usd&#34;: amount_usd,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                &#34;qty&#34;: qty,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                &#34;reason&#34;: reason,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            })
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            tool_context.state[&#34;trades_this_session&#34;] = trades
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span>         return f&#34;Success: {side.upper()} order placed for {symbol}. Order ID: {order.id}&#34;
</span></span><span style="display:flex;"><span>     except Exception as e:
</span></span><span style="display:flex;"><span>         return f&#34;Failed to place {side} order for {symbol}: {str(e)}&#34;
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span> async def save_to_memory(callback_context: CallbackContext):
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    # Full transcript: general cross-session recall (news context, reasoning).
</span></span></span><span style="display:flex;"><span>     await callback_context.add_events_to_memory(
</span></span><span style="display:flex;"><span>         events=callback_context.session.events,
</span></span><span style="display:flex;"><span>         custom_metadata={&#34;force_flush&#34;: True},
</span></span><span style="display:flex;"><span>     )
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    # Structured trade record: a clean, guaranteed memory of exactly what was
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    # traded and why, built from the orders captured in place_trade_order.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    trades = callback_context.state.get(&#34;trades_this_session&#34;, [])
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    if not trades:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        return
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    lines = []
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    for t in trades:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        size = f&#34;${t[&#39;amount_usd&#39;]}&#34; if t.get(&#34;amount_usd&#34;) else f&#34;{t[&#39;qty&#39;]} shares&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        rationale = f&#34; — {t[&#39;reason&#39;]}&#34; if t.get(&#34;reason&#34;) else &#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        lines.append(f&#34;{t[&#39;side&#39;].upper()} {t[&#39;symbol&#39;]} {size}{rationale}&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    text = &#34;Trades executed this session:\n&#34; + &#34;\n&#34;.join(lines)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    print(text, flush=True)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    await callback_context.add_memory(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        memories=[MemoryEntry(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            content=types.Content(role=&#34;model&#34;, parts=[types.Part(text=text)]),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            author=&#34;TradingAgent&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+            timestamp=datetime.now(timezone.utc).isoformat(),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        )],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        # Set a TTL of ~1 month (30 days) so the trading history can be recalled
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        # for a month while avoiding too much clutter.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        custom_metadata={&#34;type&#34;: &#34;trade&#34;, &#34;force_flush&#34;: True, &#34;ttl&#34;: &#34;2592000s&#34;},
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span> # Define the Agent
</span></span><span style="display:flex;"><span> root_agent = Agent(
</span></span><span style="display:flex;"><span>     name=&#34;TradingAgent&#34;,
</span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -172,6 +224,11 @@
</span></span></span><span style="display:flex;"><span>        - Rank by positivity.
</span></span><span style="display:flex;"><span>        - Create market BUY trades for EXACTLY $1000 of EACH of **up to 5** stocks with highest scores.
</span></span><span style="display:flex;"><span>        - Stop if available cash is less than $1000.
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    IMPORTANT: Every time you call `place_trade_order` (buy or sell), pass the `reason`
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    argument with a concise rationale: the ticker, its sentiment score, and a one-line
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    news summary. This is saved to memory so future sessions can recall why each trade
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    was made.
</span></span></span><span style="display:flex;"><span>     
</span></span><span style="display:flex;"><span>     6. FINAL REPORT:
</span></span><span style="display:flex;"><span>        Produce a comprehensive summary:
</span></span></code></pre></div>
  </div>
</details>


Now every trading run leaves behind two kinds of memory: the fuzzy transcript for the model to draw on, and a precise, structured record of exactly what we traded and why. Start a fresh session, ask “what did you recently trade?”, and it’ll answer from that record reliably — not just when the embedding model happened to hold on to it.

The tradeoff is that you’re now writing the memories that matter by hand — and deciding how long they should live. It’s a little more code, but for a trade record that genuinely needs to be there every time, it’s worth it.

<!--
TODO: uncomment when /ap/3-containerizing/3.1-adk-container/ is published.

That’s cross-session memory sorted. Up to now, though, something else has decided how the agent runs — `adk run` locally, or the deploy script that packages it for Agent Platform runtime. Next up: [containerizing the agent](/ap/3-containerizing/3.1-adk-container/) into one plain image you can run on your laptop, Cloud Run, GKE, or back on Agent Platform runtime.

-->

