
    WjZ                       U d Z ddlmZ ddlZddlZddlZddlZddlZej                  j                  dd      j                  d      Zej                  j                  dd      Zej                  j                  dd	      Zd4d
ZdZdZd5dZd6dZddddddddZdddddddddddddZd7dZd8dZd9d:d Zd;d<d!Zd=d<d"Zd>d#Zd?d@d$ZdAd%ZdBd&ZdCd'ZdDdEd(Z dFd)Z!dGdHd*Z"dId+Z#dJd,Z$dKd-Z%	 	 dL	 	 	 dMd.Z&dd/d0Z'de(d1<   dNd2Z)d3 Z*y)Ou  Jarvis long-term memory — thin client to the Railway brain.

This is the CLIENT side: it does NO heavy work. It calls the always-on memory
service (a FalkorDB-backed fact graph deployed on Railway — see memory-service/)
over HTTPS. The SERVER now owns the whole WRITE pipeline: on /remember it dedups
against what's already stored and decides add / update (supersede) / no-op
(Mem0-style) via DeepSeek, extracts the event date (``when``) and entities +
relations from the text, and cleans raw utterances into atomic facts. So the
client just ships the note (fire-and-forget) and the graph does the thinking;
searching returns ranked, linked context in ~100ms, with reinforcement-on-access
and optional temporal (`when_from`/`when_to`) filtering.

This is the cross-device brain: the Mac, the Windows box, and the phone all
point at the SAME MEMORY_API_URL, so a fact learned on one device is known on
all of them, and linked to everything else.

Runs IN-PROCESS via the Agent SDK MCP server (key ``"memory"``; Claude sees
``mcp__memory__<name>``). Pure stdlib HTTP — no heavy deps on the device.

Config (``.env``):
  MEMORY_API_URL   the deployed service, e.g. https://jarvis-memory.up.railway.app
  MEMORY_API_KEY   the shared bearer key (matches the service)
  MEMORY_GROUP     namespace (default "ahmed") — lets one brain serve many users
  MEM_SERVER_PIPELINE  set 0 to fall back to the OLD client-side supersede path
                       (the server owns dedup/supersede by default)
Set ``MCP_MEMORY=0`` to disable.
    )annotationsNMEMORY_API_URL /MEMORY_API_KEYMEMORY_GROUPahmedc                 h    t         j                  j                  dd      dk(  ryt        t              S )z0On only when a memory service URL is configured.
MCP_MEMORY10F)osenvirongetbool_URL     B/Users/ahmed/devFolder/Ultron/claude-voice/voice/memory_control.pyenabledr   *   s&    	zz~~lC(C/:r   )maybemightpossiblyperhapszi thinkznot surearoundroughlyapproximatelyzor soprobablyzcould bezi guess)
signed	confirmed
definitelyzfor sureagreedpaid	committed	finalizedzclosed the dealz	locked inc                    | xs dj                         t        fdt        D              ryt        fdt        D              ryy)u   Light heuristic — hedged facts recall weaker, firm facts stronger. The
    service uses this in salience ranking (latest+confident wins), never to drop.r   c              3  &   K   | ]  }|v  
 y wNr   .0wts     r   	<genexpr>z_confidence.<locals>.<genexpr><        
!5a165   g?c              3  &   K   | ]  }|v  
 y wr(   r   r)   s     r   r-   z_confidence.<locals>.<genexpr>>        
"6a166r/   g      ?g      ?lowerany_FIRM_HEDGEtextr,   s    @r   _confidencer9   8   s<     
A

!5
!!

"6
""r   c                    | xs dj                         t        fdt        D              ryt        fdt        D              ryy)z1-10 importance the server weighs in dedup/salience. Reuses the FIRM/HEDGE
    heuristic: firm/committed facts matter more (8), hedged guesses less (3).r   c              3  &   K   | ]  }|v  
 y wr(   r   r)   s     r   r-   z_importance.<locals>.<genexpr>G   r.   r/      c              3  &   K   | ]  }|v  
 y wr(   r   r)   s     r   r-   z_importance.<locals>.<genexpr>I   r1   r/         r2   r7   s    @r   _importancer@   C   s<     
A

!5
!!

"6
""r         r>      r?      )mondaytuesday	wednesdaythursdayfridaysaturdaysunday   r<   	   
         )januaryfebruarymarchaprilmayjunejulyaugust	septemberoctobernovemberdecemberc           	        ddl ddl}| xs dj                         }j                  j	                         }dd}dfd}|j                  d|      }|rl	 j                  t        |j                  d            t        |j                  d            t        |j                  d	                  } ||       ||      fS d
|v r ||       ||      fS d|v r%|j                  d      z
  } ||       ||      fS d|v rF|j                  |j                               z
  }	 ||	       ||	j                  d      z         fS d|v rI|j                  |j                         dz         z
  }	 ||	       ||	j                  d      z         fS d|v r(|j                  d      }	 ||	       | ||	            fS d|v rE|j                  d      j                  d      z
  }
 ||
j                  d             ||
      fS |j                  d|      }|rU|j                         t        |j                  d         z
  dz  xs d}|j                  |      z
  } ||       ||      fS |j                  d|      }|rkt        |j                  d         }||j                  k  r|j                  n|j                  dz
  }j                  ||d      }	 ||	       | ||	            fS y# t        $ r Y >w xY w)u,  Best-effort: turn a relative-time phrase in the query into an ISO date
    window (when_from, when_to) for /search. English phrases + ISO numeric dates
    only — deliberately simple, no deps, returns (None, None) if nothing time-like
    is present. Kept in sync with the copy in gateway/tools.py.r   Nr   c                "    | j                         S r(   )	isoformat)ds    r   _isoz_temporal_range.<locals>._iso`   s    {{}r   c                    | j                  d      j                  d      z   j                  d      j                  d      z
  S )NrA   day    days)replace	timedelta)r`   _dts    r   
_month_endz#_temporal_range.<locals>._month_endc   sG    		a	 3==b=#99BBqBImmm#$ 	$r   z\b(\d{4})-(\d{2})-(\d{2})\brA   rB   r>   today	yesterdayrf   z	this weekrD   z	last weekrL   z
this monthrc   z
last monthzH\b(?:on\s+)?(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\bz`\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b)NN)r`   
'_dt.date'returnstr)r`   rn   ro   rn   )datetimerer3   daterl   searchintgroup
ValueErrorri   weekdayrh   	_WEEKDAYS_MONTHSmonthyear)q_rer,   rl   ra   rk   mr`   ystartenddeltamor|   rj   s                 @r   _temporal_ranger   V   s   
 	
bAHHNNE$ 	

115A	QWWQZ#aggaj/3qwwqz?KA7DG## !|E{DK''aCMMqM))AwQa5==?;;E{DA)>!>???a5==?Q+>??E{DA)>!>???q!$E{DE!2333qmmm"S]]]%::CKKAK&'c22

 )*+	-A9QWWQZ#88A=BCMMuM--AwQ

 DEF	HAQWWQZ 5;;.uzzEJJNr1%E{DE!2333A  		s   A*K4 4	L Ldictc                    dd| dgiS )Ncontentr8   )typer8   r   )msgs    r   _textr      s    5677r   c                   |#t        j                  |      j                         nd }t        j                  j                  t        |z   ||       }t        r|j                  ddt                |j                  dd       t        j                  j                  ||      5 }|j                         }|rt        j                  |      ni cd d d        S # 1 sw Y   y xY w)N)datamethodAuthorizationzBearer zContent-Typezapplication/jsontimeout)jsondumpsencodeurllibrequestRequestr   _KEY
add_headerurlopenreadloads)r   pathbodyr   r   reqrraws           r   _reqr      s    (,(84::d""$dD
..
 
 4
 
GC'$(89NN>#56			W		5ffh"%tzz#2 
6	5	5s   #)CCc                    t         j                  j                  t        | d      }t	        dd|       j                  dg       S )z5Newest-first raw brain contents (browse, not search).)rv   limitGETz
/memories?memoriesr   parse	urlencode_GROUPr   r   r   qss     r   list_recentr      s<    			&5 A	BBB4()--j"==r   c                    t         j                  j                  t        | dd      }t	        dd|       j                  dg       S )zFUnsurfaced findings from the nightly reflection engine (newest first).true)rv   r   unsurfaced_onlyr   z
/insights?insightsr   r   s     r   fetch_insightsr      sC    			&54:!< 
=BB4()--j"==r   c                2    | rt        dd| t        d       yy)z7Mark an insight surfaced so Jarvis raises it only once.POST/insight/seenidrv   Nr   r   )
insight_ids    r   mark_insight_seenr      s    V_Z&&IJ r   c                    t         j                  j                  | |t        d      }t	        dd|       j                  dg       S )Nr}   krv   r   /search?factsr   )r}   r   r   s      r   search_factsr      s=    			aa& A	BB"'++GR88r   c                ,    t        dd| t        d       y)z"Permanently delete a wrong memory.r   z/forgetr   Nr   )mem_ids    r   forgetr      s    6F;<r   c           	     R    t        dd| |t        dd      }|j                  dd      S )z:Correct a memory: retire the old, add the new (supersede).r   
/supersedejarvis)old_idr8   rv   sourcer   r   )r   r   r   )r   r8   outs      r   editr      s2    
v| $"$%C 774r   c                d   | D cg c]  }|j                  d      r|j                  dd      |j                  d|j                  dd            |j                  d      xs d|j                  d      xs ddd |j                  d	      xs dt        |j                  d
            d c}S c c}w )z<Normalise /search facts and /memories rows to one HUD shape.r   r   factr8   kind	refers_toNrN   
created_atlinked)r   r8   r   whenr   r   )r   r   )itemsfs     r   _normr      s     % !d eeD"ofaeeFB/0f#{#)r3B/eeL)/RquuX' % % %s   B%B-c                    ddl m} 	 | j                         rt        |       n	t	               } |d| t        |             y# t        $ r/}t        dt        |      dd          |d| g        Y d}~yd}~ww xY w)uG   Push memories to the HUD viewer — search results if `q`, else recent.r   emitr   )r}   r   z!  [memory] panel refresh failed: Nd   )	voice.eventsr   stripr   r   r   	Exceptionprintrp   )r}   r   r   es       r   refresh_panelr      sh    !(#$779Q+-Z1E%L1 (1#a&#,@AZ1B''(s   9A 	A:%A55A:c                     t         j                  j                  dt        i      } t	        dd|        }|j                  dg       |j                  dg       dS )u?   Full memory graph — nodes + edges — for the HUD graph view.rv   r   z/graph?nodesedgesr   r   r   )r   r   s     r   fetch_graphr      sN    			& 1	2B
utn
%CWWWb)CGGGR4HIIr   c                &    t        dd| ||d       y)z"Connect two memories in the graph.r   z/link)from_idtor   Nr   )abr   s      r   linkr      s    aq$?@r   c                $    t        dd| |d       y)z8Cut the edge(s) between two memories (either direction).r   z/unlink)r   r   Nr   )r   r   s     r   unlinkr      s    34r   c                T   | D cg c]  }|j                  d      rt        |j                  dd            t        |j                  d|j                  dd            xs d      t        |j                  d      xs d      t        |j                  d      xs d      d c}S c c}w )z6Normalise /graph nodes to one HUD shape (all strings).r   r   r8   r   r   r   )r   r8   r   r   )r   rp   )r   ns     r   _norm_nodesr      s     %
 !d	 !%%b/"AEE&!%%"34:;AEE&M'R(!%%-34	
 % % %s   BB%c            	        ddl m}  	 t               } | dt        |j	                  dg             |j	                  dg              y# t
        $ r/}t        dt        |      dd	          | dg g        Y d}~yd}~ww xY w)
z;Push the memory graph to the HUD viewer as a `graph` event.r   r   graphr   r   r   z!  [memory] graph refresh failed: Nr   )r   r   r   r   r   r   r   rp   )r   gr   s      r   refresh_graphr     st    !*MWKgr(:;55"%	' *1#a&#,@AWBb))*s   >A 	A?%A::A?c                     t               r j                         syddl}d fd}|j                  |d      j	                          y)u  Fire-and-forget save — POSTs in a daemon thread and returns instantly, so
    the conversation NEVER pauses to remember. Used by the inline
    <remember>…</remember> path (the brain drops the note mid-reply; the engine
    ships it here without a tool round-trip). Failures are logged, not raised.
    `when` = the event date (ISO) if the caller knows it (else the server extracts
    it from the text); `tone` = the vocal delivery the fact was said with.

    The SERVER now owns dedup/supersede/cleanup (Mem0-style add/update/no-op), so
    the default path is a plain /remember. Set MEM_SERVER_PIPELINE=0 to restore
    the old client-side supersede round-trip (see memory_supersede.py).Nr   c                    	 j                         } g }t        j                  j                  dd      dk7  r	 ddlm} |j                  |       }| t        dt        |       t        |       d}
r
|d<   	r	|d	<   |r||d
<   t        j                  j                  dd      dk(  rvt        j                  j                  dd      dk7  rSd }	 ddlm} |j                  | t        | d            }|r+||d<   t        dd|       t        d|d d  d| d d  d       y t        dd|       y # t        $ r Y w xY w# t        $ r d }Y Ww xY w# t        $ r$}t        dt!        |      d d         Y d }~y d }~ww xY w)NMEM_ENTITIESr   r   r   )memory_entitiesr   )r8   rv   r   r   
confidence
importancer   toneentitiesMEM_SERVER_PIPELINEMEM_SUPERSEDE)memory_supersederD   )r   r   r   r   z  [memory: superseded r<   u    ← "2   z"]	/rememberz#  [memory] background save failed: x   )r   r   r   r   voicer   extractr   r   r9   r@   r   find_supersededr   r   r   rp   )r,   r   r   r   r   r   r   r   r8   r   r   s          r   _gozfire_save.<locals>._go  s   '	H

A  Hzz~~nc2c95.66q9H (D"-a.APD#V#V#+Z  zz~~3S9S@

<C"6-==<Q/1F %+DNt426"1:,gafXSQRd+7 ! ( ! "!F"  	H7At~FGG	Hse   5E D+ A<E #D: 0,E E +	D74E 6D77E :EE EE 	E8E33E8T)targetdaemonro   None)r   r   	threadingThreadr   )r8   r   r   r   r
  r  s   ````  r   	fire_saver    sC     9DJJL(H (HX C-335r   g        )r8   at_CORE_CACHEc                    t               syddl} | j                         }t        d   r|t        d   z
  dk  r	t        d   S 	 t        j                  j                  dt        i      }t        dd	| d
      }|j                  d      xs dj                         }|t        d<   |t        d<   |S # t        $ r t        d   cY S w xY w)u4  The ~200-word pinned profile of Ahmed the nightly reflection maintains
    server-side (GET /core_block → {text, updated_at}). Cached 30 min; returns ""
    on ANY error and never blocks more than ~2s — safe to call while building the
    voice brain's system prompt without stalling or crashing startup.r   r   Nr8   r  i  rv   r   z/core_block?rB   r   )r   timer  r   r   r   r   r   r   r   r   )r  nowr   r   r8   s        r   get_core_blockr  P  s    
 9
))+C6s[%66=6""#\\##Wf$565L-q9%2,,. KKK	  #6""#s   AB/ /CCc            	        ddl ddlm} m}  | ddt        t        t        t        d      dfd       } | dd	t        t        t        t        d
      dfd       } | ddt        t
        d      dfd       } |dd|||g      S )z6Build and return the in-process ``memory`` MCP server.r   N)toolcreate_sdk_mcp_servermemory_rememberuB  EXPLICITLY save one durable fact to Ahmed's shared memory — for memory CURATION work (dossier-card corrections, merges, backfills), NOT for conversational saving (that happens passively). `entities` = comma-separated people/companies/projects the fact is about; `when` = ISO date the fact refers to (event time), if any.)r8   r   r   r   c           	       K   t        | j                  dd            j                         }|st        d      S 	 |t        dt        | j                  dd            j                         xs ddd}| j                  d	      rt        | d	         j                         |d	<   t        | j                  d
d            j                  d      D cg c]$  }|j                         rd|j                         i& }}|r||d
<   j                  t        dd|       d {   }|xs i j                  dd      }t        d| d|d d        S c c}w 7 3# t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr8   r   z memory_remember failed: no text.zjarvis-curationr   r   rD   )r8   rv   r   r   r   r   r   ,namer   r   opaddzsaved (z): P   zmemory_remember failed:    )	rp   r   r   r   r   split	to_threadr   r   )argsr8   r   r   entsr   r  asyncios          r   r  z%build_server.<locals>.memory_remembers  s     488FB'(..0;<<	D 6=N &"!56<<>H&"#%D xx"4<0668V488J34::3?N? -.CD779 QWWY'?  N#'Z ))$TJJC)u-B72$c$s)566N K  	D3CF4CL>BCC	DsS   7FBE )E
7$E E-E 	F
E 	E>E93E>4F9E>>Fmemory_updateu6  Use when something CHANGED — a fact is now different from what was saved (client changed, a plan moved, a preference flipped). Retires the old memory (kept as history, hidden from recall) and saves the new current one, linked to what it replaced. `old_id` from a search result; `text` = the new current fact.)r   r8   r   r   c                4  K   t        | j                  dd            j                         }t        | j                  dd            j                         }|r|st        d      S 	 ||t        d| j                  d      xs dd}| j                  d	      rt        | d	         |d	<   j                  t        d
d|       d {   }t        d|j                  dd       d      S 7 ## t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr   r   r8   z3memory_update failed: need old_id and the new text.r   r   r   )r   r8   rv   r   r   r   r   r   zUpdated (new id r   ?z).zmemory_update failed: r  )rp   r   r   r   r   r  r   r   )r   r   r8   r   r   r   r"  s         r   r#  z#build_server.<locals>.memory_update  s
     TXXh+,224488FB'(..0TNOO	B$dV&&1A1KVNDxx"4<0V))$dKKC+CGGD#,>+?rBCC L 	B1#a&#,@AA	BsI   A"D&AC( C&"C( %D&C( (	D1D
DDDDmemory_searchuo  Search Ahmed's long-term memory by MEANING (not keywords) and get back connected facts — use this whenever the answer might depend on something he told you before ('what should I research for my meeting', 'what did the supplier quote', 'how do I like my coffee'). Returns up to `k` facts (default 10) — READ THEM ALL; the specific/detailed one is often not first.)queryr   c           
       K   t        | j                  dd            j                         }|st        d      S 	 ddlm} |j                  |       	 t        dt        t        | j                  d      xs d      d	            }||t        d
}t        |      \  }}|r||d<   |r||d<   t        j                  j                  |      }j!                  t"        dd|        d {   }|j                  dg       }	g }
|	D ]j  }|j                  d      rdnd}|j                  d      xs dd d }|
j%                  d|d    | |rd| dndz   d|j                  dd       dz          l 	 t        j                  j                  |dt        d
      }j!                  t"        dd|        d {   j                  dg       D ]^  }|
j%                  d|j                  dd       d       j!                  t"        d d!|j                  dd      t        d"       d {    ` 	 	 dd l}h d#|gt)        fd$|j+                  d%|j-                               D        t.        d&'      d d z   }|D ]  }t        j                  j                  |t        d(      }j!                  t"        dd)|        d {   j                  d*g       }t1        d+ |D        d       }|sq|j                  d,      r|
j%                  d-|d.    d/|d,    d       n/|j                  d0      r|
j%                  d1|d.    d/|d0    d        n |
st        d2      S t        d3j3                  |
            S # t        $ r Y *w xY w7 7 7 c# t        $ r Y jw xY w7 # t        $ r Y ]w xY w# t        $ r$}t        d4t        |      d d5        cY d }~S d }~ww xY ww)6Nr'  r   zmemory_search failed: no query.r   )entity_showrA   r   rN      r   	when_fromwhen_tor   r   r   r   z [connected]r   z- r   z  (re: )z  [id r   r%  ]rB   z/insights/search?r   u{   - (a reflection of yours that may be relevant here — mention it ONLY if it genuinely fits, briefly, as your own thought: r8   r   r   r   >   costdealdoesfromhavemuchtellthatthiswhatr   withaboutthinkclientshouldapproachc              3  *   K   | ]
  }|vr|  y wr(   r   )r*   r+   stops     r   r-   z6build_server.<locals>.memory_search.<locals>.<genexpr>  s       ' E1  Es   z	[a-z]{4,}T)keyreverse)r  rv   z/entity?r   c              3  d   K   | ](  }|j                  d       s|j                  d      r| * yw)r   summaryN)r   )r*   r   s     r   r-   z6build_server.<locals>.memory_search.<locals>.<genexpr>  s-       F4a#$55=AEE)4D !"4s   .0r   z- (your read on r  z: rD  z- (on z (nothing relevant in memory yet)
zmemory_search failed: r  )rp   r   r   r   r  r)  show_for_queryr   maxminru   r   r   r   r   r   r  r   appendrr   sortedfindallr3   lennextjoin)r   r}   _esr   paramswfwtr   r   r   linesr   tagr   iqsit_re2triesr  eqsr!  hitr   r@  r"  s                          @r   r&  z#build_server.<locals>.memory_search  s     "%&,,.:;;	0q!?	BAs3txx}23R89A1v6F$Q'FB&({#$&y!''/B))$"GGCGGGR(EE()hnRk*0b#26r!F)SE259'$q 1rC!'dC(8'9;< = ll,,11v-NO!(!2!2e'8%>"@ @ADZQSATUBLL((*vr(:';1>? "++D&/3566$3Cf,UW W WU!B f'\1779 E ',/??AC C "D ,,00$1PQC")"3"3exu%5#7 78;J8K   F4  FGKMC776?!LL+;CK=,/K=*; < WWY/!LL6#f+bY@PPQ)RS " ?@@5)**  		 H@W 7  
  	B1#a&#,@AA	Bs   7OM1 BN+ (N)BN+ 1AN
 5N6A.N
 $N%N
 +N+ -BN =N>)N (A"N 
N+ ON+ 0O1	M>:O=M>>ON+ N
 N
 
	NN+ NN+ N 	N(%N+ 'N((N+ +	O4OOOOOmemoryz1.0.0)r  versiontools)r   r   ro   r   )r"  claude_agent_sdkr  r  rp   ru   )r  r  r  r#  r&  r"  s        @r   build_serverr_  f  s    < 

G
 Sc
BDDDD( 
/3
 #sC
@BBBB 
/- c
"$KB$KBZ !}= r   )ro   r   )r8   rp   ro   float)r8   rp   ro   ru   )r}   rp   ro   ztuple[str | None, str | None])r   rp   ro   r   )N   )r   rp   r   rp   r   zdict | Noner   ru   )(   )r   ru   ro   
list[dict])rA   )r   rp   ro   r	  )   )r}   rp   r   ru   ro   rc  )r   rp   ro   r	  )r   rp   r8   rp   ro   rp   )r   rc  ro   rc  )r   )r}   rp   ro   r	  )ro   r   )related)r   rp   r   rp   r   rp   ro   r	  )r   rp   r   rp   ro   r	  )r   rc  ro   rc  r  )r   NN)
r8   rp   r   rp   r   
str | Noner   rf  ro   r	  )ro   rp   )+__doc__
__future__r   r   r   urllib.errorr   urllib.parseurllib.requestr   r   rstripr   r   r   r   r6   r5   r9   r@   ry   rz   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  __annotations__r  r_  r   r   r   <module>rn     sP  8 #  	   	zz~~&+2237	zz~~&+		0
!	C QQAa16	QQqa11bb:
6r8.">>K9
=
	%($JA
5
%	* AE!%<6<6*.<6~  s+T +,Wr   