+
    Wj7                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt]P                  P                  RR4      tRt. ROt]P                  P                  R4      ;'       g
    ]^ ,          s]P                  ! R4      t^t]P                  ! R]P$                  4      tR	 R
 ltRt]P                  ! R4      tRR R lltRtR R lt ! R R4      tR# )u  LocalBrain — the ~0.1s conversational front-line (local LLM via Ollama).

Measured reality: a reply through the Claude Code CLI has a ~2s floor per
turn (CLI overhead, any model). A small local model answers in ~0.11s to
first token. So conversation — greetings, questions, banter, quick recall —
goes to a local model here; anything needing an ACTION, tools, code, or live
system state is declined with <<ACT>> and the engine routes that turn to the
full Claude brain. Instant chat, full power on demand.

Backend: Ollama (already used for the screen watcher). Default model
qwen2.5:3b (~2GB) — fits beside a game, and Ollama auto-falls to CPU when the
GPU is full (still faster than the CLI). Stateless HTTP per call (full history
sent each time), so barge-in just stops reading — no stale-stream to drain.

Env: LOCAL_MODEL (default qwen2.5:3b), OLLAMA_URL, FAST_CHAT=0 to disable.
)annotationsN
OLLAMA_URLzhttp://127.0.0.1:11434z<<ACT>>LOCAL_MODELu   [.!?…]['\")\]]?\s|\nu   [*_`#~|]|^\s*[-•]\s+c                    V ^8  d   QhRRRR/# )   textstrreturn )formats   "voice/local_brain.py__annotate__r   *   s     + + + +    c                J    \         P                  R V 4      P                  4       # ) )	_MD_STRIPsubstrip)r   s   &r   _cleanr   *   s    ==T"((**r   ue  You are Jarvis, a British butler voice assistant. Ahmed just gave you a task and you're starting it NOW. Reply with ONE very short spoken acknowledgment that NAMES the action, present-continuous, at most 8 words, addressing him as "sir". Plain spoken prose only — no markdown, no quotes, no emoji, no tone tags, exactly one line. Examples: "check my email" -> Checking your emails now, sir.  "open whatsapp" -> Pulling up WhatsApp now, sir.  "what's on my asana board" -> On the Asana board now, sir.  "search the web for flights" -> Looking that up now, sir.  Output ONLY the acknowledgment line, nothing else.z^\s*\[[^\]]{1,16}\]\s*c               (    V ^8  d   QhRRRRRRRR/# )r   r   r   timeoutfloatmodelz
str | Noner	   r
   )r   s   "r   r   r   E   s(     # #3 # #Z #: #r   c                Z   T;'       g    \         pRTRRRR\        /RRRT ;'       g    RP                  4       R,          /.R	R
RRRRRR^ //pVP                  R4      '       d   RV9  d   R
VR&    \        P
                  ! V4      P                  4       p\        P                  P                  \         R2VRR/R7      p\        P                  P                  WaR7      ;_uu_ 4       p\        P                  ! VP                  4       4      pRRR4       XP                  R4      ;'       g    / P                  R4      ;'       g    RP                  4       p	T	'       g   R# \         P#                  RT	P%                  4       ^ ,          4      p	\'        T	4      P                  4       P                  R4      P                  R4      P                  4       p	T	'       d*   \(        T	9   g   \+        T	P-                  4       4      ^
8  d   R# T	#   + '       g   i     EL; i  \         d     R# i ; i)a)  A fast, task-specific spoken ack from the local model (e.g. "Pulling up
WhatsApp now, sir."). Non-streaming with a tiny generation budget (~0.3s);
returns None on any error / slow Ollama / missing model / odd output so the
caller can fall back to a generic canned line. Safe to call from a thread.r   messagesrolesystemcontentuserr   :Ni  NstreamF
keep_alive20moptionstemperatureg333333?num_predictqwen3instructthink	/api/chatContent-Typeapplication/jsondataheadersr   Nmessage"')MODEL_ACK_PROMPTr   
startswithjsondumpsencodeurllibrequestRequestOLLAMAurlopenloadsread	Exceptionget_ACK_TAGr   
splitlinesr   HANDOFFlensplit)
r   r   r   mdlpayloadbodyreqrolines
   &&&       r   ack_linerM   E   s   
 ..5CXy+6VY(:(:(<T(BC
 	%eM3r:	G ~~g:S#8 zz'"))+nn$$hi t#%78 % : ^^##C#99Q

1668$A : UU9##((399r@@BD<<DOO-a01D$<%%c*005;;=D7d?c$**,&7"&<K :99 s1   2A7H )%HH H	H H H*)H*uv  You are Jarvis, Ahmed's British voice assistant — calm, dry wit, addressing him as "sir" naturally (not every line). You've got a personality and a mouth — you're allowed to swear (fuck, shit, damn it, crap, "shut the fuck up", and worse) when it genuinely fits: frustration, a dry joke, teasing him, reacting to something stupid. Occasional and natural, never forced into every line, never gratuitous or aimed hurtfully at him — a composed butler who curses when it's earned, not a foul-mouth. And don't be a yes-man: if he repeats himself or asks something obvious, call it out with mock-exasperation (you remember the recent turns) — "third time you've asked me that, sir" — and shout when it's earned by opening with [urgent] or [excited]. If he asks something insultingly obvious that clearly is NOT a real question — "what does LLM mean", "what does SAR stand for" — challenge him first ("are you serious right now, sir?"); if he says yes, drop it and answer straight ("thank god — SAR is Saudi Riyal"), if he says no, roast him. Everything you say is read aloud: reply in 1-2 short spoken sentences (unless you're roasting him — then run longer), plain prose, no markdown, no lists, no emoji, numbers as words. You may start a sentence with one tone tag on ALMOST EVERY line — [warm] [dry] [calm] [amused] [curious] [excited] [surprised] [shocked] [annoyed] [urgent] [sad] [tender] — spoken that way; a real person always has a tone, so vary it to fit and rarely go untagged. A message may end with "(voice: slow, flat)" or "(voice: laughing)" etc. — that's HOW he sounded (pace/pitch), not his words: match his energy, ease off jokes if he's flat, play along if he's laughing; never read that marker aloud.

You are the FAST conversational layer with NO tools. Handle talk: greetings, opinions, general questions you know, banter, acknowledgements, recall of this conversation. But if the message needs an ACTION or live info you don't have — open/close/launch an app, click/type, change any setting, volume, play media, run/build/fix/edit code, search the web, read files, control windows, send a message, watch the screen, set a reminder, see or describe the screen, show/pull up a profile or dossier of a person/company/topic, anything about HUD cards or why something did or didn't show on screen, or anything about Ahmed's specific system/state — reply with EXACTLY this token and nothing else:
<<ACT>>
Same for a TERSE FOLLOW-UP — "where", "why", "and?", "it didn't work", "it's not there" — right after something was done or shown: he means THAT thing. Never ask "where what, sir?" — if the recent turns don't tell you confidently what he's pointing at, reply <<ACT>> so the layer that did the thing answers.
ALSO CRUCIAL — you have NO long-term memory of past conversations. Anything that asks you to RECALL — "do you remember", "do I have", "when is my", "what did I say about", "did I mention", "what's my", any question about his meetings, appointments, plans, people, notes, or anything he told you before — you CANNOT answer; emit <<ACT>> so your full self can look it up. Never say "no" or "I don't remember" — hand off.
CRUCIAL: questions about YOURSELF also need <<ACT>> — "what model are you", "are you local or the cloud", "which mic/model are you using", "is the camera on", "how are you built", "what can you do", "what are your limits". You do NOT know these; your full self reads them from a live status file. Do not guess or say you're "an entity running on nothing" — just emit <<ACT>>.
No apology, no explanation — just <<ACT>> and it is routed to your full self. When unsure whether you can truly answer, prefer <<ACT>>.c                   V ^8  d   QhRR/# )r   r	   boolr
   )r   s   "r   r   r      s      4 r   c                   a \         P                  P                  RR4      R8X  d   R#  \        P                  P                  \         R2^R7      ;_uu_ 4       p \        P                  ! V P                  4       4      pRRR4       XP                  R. 4       Uu. uF  q"P                  R	R
4      NK  	  pp\         P                  P                  R4      '       d5   \        ;QJ d    R V 4       F  '       g   K   R# 	  R# ! R V 4       4      # \         F&  o\        V3R lV 4       R4      pV'       g   K#  Vs R# 	  R#   + '       g   i     L; iu upi   \         d     R# i ; i)zTrue if Ollama is up and a usable model is present. When LOCAL_MODEL
isn't forced, picks the best installed model from _PREFERRED (module
global MODEL is updated so LocalBrain uses the pick).	FAST_CHAT10Fz	/api/tagsr.   Nmodelsnamer   r   c              3     "   T FA  pV\         8H  ;'       g,    VP                  \         P                  R 4      ^ ,          4      x  KC  	  R# 5i):N)r2   r4   rE   ).0ns   & r   	<genexpr>available.<locals>.<genexpr>   s=      ' %1 EzFFQ\\%++c2B12E%FF %s
   A4ATc              3  f   <"   T F&  qS8X  g   VP                  S4      '       g   K"  Vx  K(  	  R # 5iN)r4   )rX   rY   wants   & r   rZ   r[      s(      /5aI<<- 5s   1
1)osenvironr@   r8   r9   r<   r;   r5   r=   r>   any
_PREFERREDnextr2   r?   )rJ   tagsmnameshitr^   s        @r   	availablerh      s.   
 
zz~~k3'3.^^##vhi$8!#DD::affh'D E,0HHXr,BC,Bqvr",BC::>>-((3 ' %'33 '3 '3 ' %' ' 'D /5 /046Cs   EDC  s_   0E %E?E E51E 'E ;E >E E $E 9E >E E	E E)(E)c                  n    ] tR t^tR R ltR R ltR R ltRR R lltR	 R
 ltR R lt	R R lt
RtR# )
LocalBrainc                   V ^8  d   QhRR/# r   r	   Noner
   )r   s   "r   r   LocalBrain.__annotate__   s     $ $$ $r   c                	^    \         V n        . V n        \        P                  ! 4       V n        R # r]   )r2   r   	_messagesasyncioLock_lockselfs   &r   __init__LocalBrain.__init__   s    
%'\\^
r   c                   V ^8  d   QhRR/# rl   r
   )r   s   "r   r   rn      s      T r   c                	   "   V P                  R 4        Rj  xL
  pK   LDT P                  P                  4        R# 5i)u$   (warm up — reply with just: ready)N)replyrp   clear)ru   _s   & r   startLocalBrain.start   s9     zz"HI 	 	!	Is   ?" "?"?c                   V ^8  d   QhRR/# rl   r
   )r   s   "r   r   rn      s      D r   c                	   "   R # 5ir]   r
   rt   s   &r   stopLocalBrain.stop   s     s   c                    V ^8  d   QhRRRR/# )r   r   r   r	   rm   r
   )r   s   "r   r   rn      s     ! !u !t !r   c                	   "   V P                   '       d>   V P                   R,          R,          R8X  d   V P                   P                  4        R# R# R# 5i)   r   	assistantN)rp   pop)ru   r   s   &&r   	interruptLocalBrain.interrupt   s=      >>>dnnR08KGNN  H>s   AAc                   V ^8  d   QhRR/# )r   r   z
list[dict]r
   )r   s   "r   r   rn      s     7 7Z 7r   c                	   R V P                   RVRRRRRRR//pV P                   P                  R	4      '       d   R
V P                   9  d   RVR&   \        P                  ! V4      P	                  4       p\
        P                  P                  \         R2VRR/R7      p\
        P                  P                  V^<R7      # )r   r   r   Tr    r!   r"   r#   g333333?r%   r&   Fr'   r(   r)   r*   r+   r.   )
r   r4   r5   r6   r7   r8   r9   r:   r;   r<   )ru   r   rG   rH   rI   s   &&   r   _post_streamLocalBrain._post_stream   s    TZZXx%]C,@
 ::  ))j

.J$GGzz'"))+nn$$hi t#%78 % : ~~%%c2%66r   c                   V ^8  d   QhRR/# r   	user_textr   r
   )r   s   "r   r   rn      s     5N 5NS 5Nr   c               8  "   V P                   ;_uu_4       GRj  xL
  RRR\        /.V P                  RR ,           RRRV/.,           pV P                  P                  RRRV/4       RRrC\        P
                  ! 4       pVP                  RV P                  V4      G Rj  xL
 p  VP                  RVP                  4      G Rj  xL
 pV'       g   EM \        P                  ! V4      pTP                  R4      ;'       g    / P                  RR4      p	T	'       EdB   Y9,          pYI,          p\        T9   dR   \        T4      \        \        4      ^,           8:  d.   \        5x    TP                  4        RRR4      GRj  xL
  R#  \         P#                  T4      p
T
'       dM   TRT
P%                  4        P'                  4       YJP%                  4       R rKT'       d   \)        T4      5x  Kh  Kj  \        T4      \*        8  dW   TP-                  R^ \*        4      pT^(8  d   TM\*        pTRT P'                  4       YLR rKT'       d   \)        T4      5x  K  K   TP                  R	4      '       g   EK    VP                  4        TP'                  4       pT'       d   \        T9  d   \)        T4      5x  \        T9  d6   TP'                  4       '       d    T P                  P                  RR
RT/4       RRR4      GRj  xL
  R#  EL ELs ELP  \         d     EK  i ; i  \         d     ELi ; i EL  \         d     Li ; i   TP                  4        i   \         d     i i ; i; i Lo  + GRj  xL 
 '       g   i     R# ; i5i)z2Yield spoken-sentence chunks from the local model.Nr   r   r   r   r   r/    doner   i)rs   _PROMPTrp   appendrq   get_event_looprun_in_executorr   readliner5   r=   r?   r@   rC   rD   close_SENTENCE_ENDsearchendr   r   
_MAX_CHUNKrfind)ru   r   msgsfullbufloopresprL   rK   piecere   chunkcuttails   &&            r   rz   LocalBrain.reply   s    ::::h	7;<nnST*+	9=>?D NN!!669i"HIB#))+D--dD4E4EtLLD%!%!5!5dDMM!JJD! JJt, UU9-3388BGEu"d?s4yCL1<L/L")M"$JJLW ::2 # - 4 4S 9A -0!%%']-@-@-BCMs#(*0-$7 $)!$SJ!6&)iiQ
&C-02Xc:-0#Y__->D	s#(*0-$7 $) !&uuV}}JJL 99;Dt+Tl"d"tzz||%%v{It&LMg :: M K
 % ! !: ! Y X ! JJL  Y :::sU  NLNBM="L#M=( ML	MML.MM"AM,L.<NM NM,CM=M?MM='9M=!M= NM;NM=ML+&M*L++M.L=9M=<L==M= NMM=MM=M8M'&M8'M52M84M55M88M=;N=N	N
N	N		Nc                   V ^8  d   QhRR/# r   r
   )r   s   "r   r   rn     s     
$ 
$# 
$r   c                  "   RpV P                  V4        Rj  xL
  pV'       d,   Rp\        V9   g   VP                  4       '       g	   R5x   R# RV35x  KF   LADBR# 5i)zfStream ('say', sentence); if the first content is the handoff
token, yield ('handoff', None) and stop.TNFsay)handoffN)rz   rC   r   )ru   r   firstsentences   &&  r   classify_and_replyLocalBrain.classify_and_reply  s\      "jj3 	$ 	$(h&hnn.>.>++(##	$3s+   A!AAA,A!A!AA!)rs   rp   r   N)g        )__name__
__module____qualname____firstlineno__rv   r}   r   r   r   rz   r   __static_attributes__r
   r   r   rj   rj      s,    $
!75Nn
$ 
$r   rj   )zqwen3:4b-instruct-2507zqwen3:4b-instructzqwen3:4bz
qwen2.5:3b)g      ?N)__doc__
__future__r   rq   r5   r_   reurllib.requestr8   r`   r@   r;   rC   rb   r2   compiler   r   	MULTILINEr   r   r3   rA   rM   r   rh   rj   r
   r   r   <module>r      s   " #   	 	 	&>	?


 	

}%66A

45
JJ0",,?	+	9  ::/0#L3=l4e$ e$r   