# Deterministic memories In the [previous post](/ap/2-memories/2.1-add-memories/) we sent the agent's session events to Memory Bank with `add_events_to_memory()` in an after-agent callback. An LLM extracts useful facts from those events, and embeddings make the saved facts searchable in a later session. That gives us general recall, but the model may leave out the specific orders from a trading run. For this agent, I want to keep the orders and the reasons for placing them. We already have that data in the trade tool, so let's use it to write a memory ourselves. We can keep the automatic extraction alongside it for preferences and other useful context. There are two pieces to saving a memory deterministically.
The existing path extracts facts from session events with an LLM. The new path captures submitted orders in session state and formats them into one explicit memory with a 30-day expiry, skipping LLM extraction. Both paths use the same Memory Bank and embedding-based retrieval.
Scroll horizontally to explore. Open full-size diagram
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: after `submit_order()` returns, we stash the submitted order in the tool context's session state. ```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 ``` Note: The example's "confirmed fill" comment and "Trades executed" heading describe more than the code checks. It records an order submission, without waiting for the order to fill. If you want a record of actual fills, check [Alpaca's order status](https://docs.alpaca.markets/us/docs/orders-at-alpaca#order-lifecycle) or consume its trade updates before recording them. Second, in the after-agent callback, send the events for LLM extraction and write our own memory for the orders. We format the captured orders and their reasons into one `MemoryEntry`, then save it with `add_memory()`. The default [explicit-memory path](https://github.com/google/adk-python/blob/main/src/google/adk/memory/vertex_ai_memory_bank_service.py) creates the supplied fact without LLM extraction. Embeddings still support retrieval; what's deterministic here is our choice of the memory's contents. A write can still fail, and a successful write doesn't guarantee that a later query will retrieve it. ```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"}, ) ``` The `add_events_to_memory()` call is unchanged from the previous post, including `force_flush`. The new pieces are the explicit memory write and a `reason` argument on `place_trade_order`. The agent's instructions ask it to include the ticker, sentiment score, and a one-line news summary in that reason. We save the supplied rationale with the order so a later session has some context for why it was placed. The explicit memory carries a `ttl` of `"2592000s"`, or 30 days. That limits how long this copy of the order history stays in Memory Bank. The TTL is supplied only on `add_memory()` here; it doesn't change the retention of facts extracted from the session events, which may also mention an order. Tune the window for your application. The durable order history still lives in Alpaca. Note: This example writes one memory containing all orders in `trades_this_session` each time the callback reaches `add_memory()`. It doesn't clear that list after saving, so later turns in the same session can write the same orders again, with a new expiry. For repeated runs in one session, track which orders have been saved and only write new ones; retries also need a way to avoid duplicate records. The full diff to add this is as follows.
agent.py.diff
$ diff -u ../02_Memories/trading_agent/agent.py trading_agent/agent.py 
--- ../02_Memories/trading_agent/agent.py       2026-05-18 04:50:36.609821500 +0000
+++ trading_agent/agent.py      2026-06-24 00:33:46.870713112 +0000
@@ -1,7 +1,7 @@
 import os
 import sys
 import time
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
 from typing import Dict, Any, Optional
 from dotenv import load_dotenv
 
@@ -13,8 +13,11 @@
 
 from google.adk.agents import Agent
 from google.adk.agents.callback_context import CallbackContext
+from google.adk.memory.memory_entry import MemoryEntry
 from google.adk.tools.load_memory_tool import LoadMemoryTool
 from google.adk.tools.preload_memory_tool import PreloadMemoryTool
+from google.adk.tools.tool_context import ToolContext
+from google.genai import types
 from vertexai import agent_engines
 
 # Load environment variables
@@ -106,14 +109,23 @@
     quote = stock_client.get_stock_latest_quote(request_params)
     return float(quote[symbol].ask_price)
 
-def place_trade_order(symbol: str, side: str, amount_usd: Optional[float] = None, qty: Optional[float] = None) -> str:
-    """Places a market order (fractional shares supported for buys)."""
+def place_trade_order(symbol: str, side: str, amount_usd: Optional[float] = None, qty: Optional[float] = None, reason: str = "", tool_context: ToolContext = None) -> str:
+    """Places a market order (fractional shares supported for buys).
+
+    Args:
+        symbol: The ticker to trade.
+        side: 'buy' or 'sell'.
+        amount_usd: Dollar amount for a notional buy.
+        qty: Share quantity (required for sells).
+        reason: Short rationale for the trade (ticker, sentiment score, news summary).
+                Saved to memory so future sessions can recall why this trade was made.
+    """
     side = side.lower()
     if side not in ['buy', 'sell']:
         return "Error: Side must be 'buy' or 'sell'."
 
     order_side = OrderSide.BUY if side == 'buy' else OrderSide.SELL
-    
+
     try:
         if amount_usd is not None and side == 'buy':
             order_request = MarketOrderRequest(
@@ -134,16 +146,56 @@
 
         time.sleep(1)
         order = trading_client.submit_order(order_data=order_request)
+
+        # 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
+
         return f"Success: {side.upper()} order placed for {symbol}. Order ID: {order.id}"
     except Exception as e:
         return f"Failed to place {side} order for {symbol}: {str(e)}"
 
 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"},
+    )
+
 # Define the Agent
 root_agent = Agent(
     name="TradingAgent",
@@ -172,6 +224,11 @@
        - Rank by positivity.
        - Create market BUY trades for EXACTLY $1000 of EACH of **up to 5** stocks with highest scores.
        - Stop if available cash is less than $1000.
+
+    IMPORTANT: Every time you call `place_trade_order` (buy or sell), pass the `reason`
+    argument with a concise rationale: the ticker, its sentiment score, and a one-line
+    news summary. This is saved to memory so future sessions can recall why each trade
+    was made.
     
     6. FINAL REPORT:
        Produce a comprehensive summary:
After a successful save, Memory Bank has the explicit order record alongside any facts extracted from the session events. Start a fresh session and ask "what orders did you recently submit?" to try retrieving it. The memory tools and similarity search work the same way as before; this time, we chose the order details to send to the memory store ourselves.