forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchat_first_intents.py
More file actions
1587 lines (1387 loc) · 63.7 KB
/
Copy pathchat_first_intents.py
File metadata and controls
1587 lines (1387 loc) · 63.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Durable Chat-first proactive intent state, separate from the chat journal."""
import hashlib
import json
import logging
from dataclasses import dataclass
from datetime import date, datetime, timedelta, timezone
from typing import Any, Iterable, cast
from google.cloud import firestore
from google.cloud.firestore_v1 import FieldFilter
from database._client import get_firestore_client
from database import chat_first_delivery_attempts as delivery_attempts
from database.chat_first_intent_queue import drain_intent_batch, sort_ready_intents
from database.durable_queue import ProcessOutcome
from database.firestore_index_registry import (
CHAT_FIRST_DEFERRALS_DUE_QUERY,
CHAT_FIRST_DEFERRALS_SUBJECT_QUERY,
)
from database.read_boundary import MalformedDocError, parse_snapshot_strict
from models.chat_first import (
ChatFirstBlockSpec,
ChatFirstSubject,
ColdStartSequenceTerminalState,
DeferralReceipt,
ProactiveBudgetState,
ProactiveDeferral,
ProactiveIntent,
ProactiveIntentSource,
QuestionCardSpec,
)
from models.proactive_budget import account_materialization, normalized_budget_state, reserve_budget
from models.task_intelligence import TaskWorkflowControl
INTENTS_COLLECTION = 'chat_first_proactive_intents'
DEFERRALS_COLLECTION = 'chat_first_deferrals'
STATE_COLLECTION = 'chat_first_proactive_state'
DELIVERY_ATTEMPTS_COLLECTION = 'chat_first_delivery_attempts'
DEAD_LETTERS_COLLECTION = 'chat_first_dead_letters'
BUDGET_DOCUMENT = 'budget'
_DEFERRAL_DUE_AFTER = timedelta(hours=24)
CONTINUOUS_DEFERRAL_BUDGET = timedelta(days=7)
TRANSIENT_DEAD_LETTER_REPAIR_AGE = delivery_attempts.TRANSIENT_DEAD_LETTER_REPAIR_AGE
FETCH_CANDIDATE_SCAN_MULTIPLIER = 2
DEFERRAL_CANDIDATE_SCAN_LIMIT = 64
UNACKNOWLEDGED_DEAD_LETTER_REASON = delivery_attempts.UNACKNOWLEDGED_DEAD_LETTER_REASON
KERNEL_FAILURE_DEAD_LETTER_REASON = delivery_attempts.KERNEL_FAILURE_DEAD_LETTER_REASON
TRANSIENT_DEAD_LETTER_REASONS = delivery_attempts.TRANSIENT_DEAD_LETTER_REASONS
TRANSIENT_DEATH_AFTER_REQUEUE_REASON = 'transient_death_after_requeue'
logger = logging.getLogger(__name__)
# A ready intent must reach a terminal state under bounded identical retries:
# typed kernel failures park it after three reports, while fetch-only clients
# cannot keep any head item live beyond twenty unacknowledged deliveries.
MATERIALIZATION_REJECTION_BUDGET = 3
UNACKNOWLEDGED_FETCH_BUDGET = 20
STALLED_READY_AGE = timedelta(hours=24)
# A capture receipt announces one conversation that just finished processing.
# It is only ever delivered while the rich Chat transcript is foregrounded, and
# until now nothing retired one that was never delivered -- so an account that
# was not looking at Chat accrued one ready intent per finalized conversation,
# and the next foreground poll handed the kernel a whole batch of them at once.
# The kernel stamps every row it writes with its own clock, so that backlog
# lands as a run of conversation cards sharing one timestamp, days after the
# conversations they announce. Two bounds keep a receipt a live notice: an
# older receipt has been superseded by a newer one, and a receipt past the
# delivery window has no reader left. Neither loses anything, because the
# conversation itself is in the conversation list either way.
CAPTURE_RECEIPT_DELIVERY_WINDOW = STALLED_READY_AGE
STALE_CAPTURE_DEAD_LETTER_REASON = 'stale_capture_receipt'
SUPERSEDED_CAPTURE_DEAD_LETTER_REASON = 'superseded_capture_receipt'
PERMANENT_REJECTION_CODES = frozenset({'invalid_intent', 'identity_conflict'})
_SYNTHETIC_RECONCILIATION_RECEIPT_PREFIX = 'cfi_reconciled_'
@dataclass(frozen=True)
class IntentLifecycleEvent:
event: str
source: str
reason: str
@dataclass(frozen=True)
class ReadyIntentBatch:
intents: list[ProactiveIntent]
lifecycle_events: tuple[IntentLifecycleEvent, ...]
stalled_source: ProactiveIntentSource | None
@dataclass(frozen=True)
class DeferralReleaseBatch:
intents: list[ProactiveIntent]
malformed_count: int
class ChatFirstIntentStoreError(RuntimeError):
"""Base class for closed intent-store failures."""
class ChatFirstIntentGenerationMismatch(ChatFirstIntentStoreError):
pass
class ChatFirstIntentDocumentGenerationMismatch(ChatFirstIntentStoreError):
pass
class ChatFirstMalformedDocument(ChatFirstIntentStoreError):
pass
class ChatFirstIntentConflictError(ChatFirstIntentStoreError):
pass
class ProactiveBudgetExhausted(ChatFirstIntentStoreError):
pass
class ProactiveIntentNotReady(ChatFirstIntentStoreError):
pass
@dataclass(frozen=True)
class AgentJudgmentAdmission:
"""The one durable admission result that may precede a judge call.
A newly acquired reservation is the cost gate for a single judge call. An
already-pending reservation deliberately is *not* another admission: two
concurrent post-commit wakes for the same continuity key must not spend two
model calls while racing to create one intent.
"""
existing_intent: ProactiveIntent | None
newly_reserved: bool
def _db(firestore_client: Any = None) -> Any:
return firestore_client or get_firestore_client()
def _user_ref(uid: str, *, firestore_client: Any = None):
return _db(firestore_client).collection('users').document(uid)
def _control_ref(uid: str, *, firestore_client: Any = None):
return _user_ref(uid, firestore_client=firestore_client).collection('task_intelligence_control').document('state')
def _intent_ref(uid: str, intent_id: str, *, firestore_client: Any = None):
return _user_ref(uid, firestore_client=firestore_client).collection(INTENTS_COLLECTION).document(intent_id)
def _delivery_attempt_ref(uid: str, intent_id: str, *, firestore_client: Any = None):
return (
_user_ref(uid, firestore_client=firestore_client).collection(DELIVERY_ATTEMPTS_COLLECTION).document(intent_id)
)
def _dead_letter_ref(uid: str, intent_id: str, *, firestore_client: Any = None):
return _user_ref(uid, firestore_client=firestore_client).collection(DEAD_LETTERS_COLLECTION).document(intent_id)
def _deferral_ref(uid: str, deferral_id: str, *, firestore_client: Any = None):
return _user_ref(uid, firestore_client=firestore_client).collection(DEFERRALS_COLLECTION).document(deferral_id)
def _budget_ref(uid: str, *, firestore_client: Any = None):
return _user_ref(uid, firestore_client=firestore_client).collection(STATE_COLLECTION).document(BUDGET_DOCUMENT)
def _stable_id(prefix: str, *parts: object) -> str:
raw = '\x1f'.join(str(part) for part in parts).encode('utf-8')
return f'{prefix}_{hashlib.sha256(raw).hexdigest()[:32]}'
def proactive_intent_id(
uid: str,
*,
account_generation: int,
source_key: str,
continuity_key: str,
) -> str:
"""Return the canonical durable ID for one proactive intent identity."""
return _stable_id('cfi', uid, account_generation, source_key, continuity_key)
def proactive_deferral_id(uid: str, *, account_generation: int, continuity_key: str) -> str:
"""Return the canonical durable ID for one deferred question identity."""
return _stable_id('cfd', uid, account_generation, continuity_key)
def _require_control(snapshot: Any, *, uid: str, account_generation: int) -> None:
control = TaskWorkflowControl()
if snapshot.exists:
try:
control = parse_snapshot_strict(TaskWorkflowControl, snapshot)
except MalformedDocError as error:
raise ChatFirstIntentGenerationMismatch('chat-first capability state is malformed') from error
if control.account_generation != account_generation:
raise ChatFirstIntentGenerationMismatch('chat-first capability changed')
def _budget_from_snapshot(snapshot: Any, *, account_generation: int, now: datetime) -> ProactiveBudgetState:
if not snapshot.exists:
return ProactiveBudgetState(account_generation=account_generation)
try:
state = parse_snapshot_strict(ProactiveBudgetState, snapshot)
except MalformedDocError as error:
raise ChatFirstMalformedDocument('chat-first proactive budget state is malformed') from error
if state.account_generation != account_generation:
return ProactiveBudgetState(account_generation=account_generation)
return normalized_budget_state(state, now=now)
def _intent_from_snapshot(snapshot: Any) -> ProactiveIntent:
"""Load correctness-critical proactive state without treating corruption as absent."""
try:
return parse_snapshot_strict(ProactiveIntent, snapshot)
except MalformedDocError as error:
raise ChatFirstMalformedDocument('chat-first proactive intent is malformed') from error
def _deferral_from_snapshot(snapshot: Any) -> ProactiveDeferral:
"""Load correctness-critical deferred-question state without a fallback."""
try:
return parse_snapshot_strict(ProactiveDeferral, snapshot)
except MalformedDocError as error:
raise ChatFirstMalformedDocument('chat-first deferral is malformed') from error
def _require_current_control(uid: str, *, account_generation: int, firestore_client: Any) -> None:
"""Fence read-only entry points before they inspect feature-specific rows."""
_require_control(
_control_ref(uid, firestore_client=firestore_client).get(),
uid=uid,
account_generation=account_generation,
)
def _intent_payload(intent: ProactiveIntent) -> dict[str, Any]:
# Rolling-deploy safety: pre-Round-7 readers reject these newer fetch and
# repair fields. They live in a sibling document keyed by intent ID, never
# on the intent document consumed by old revisions.
delivery_fields = {
'fetch_count',
'last_fetched_at',
'requeue_count',
'materialization_attempts',
'last_rejection_code',
'last_rejection_at',
'first_deferred_at',
'last_deferral_at',
'dead_letter_reason',
}
# Old revisions also parse a receipt's intent by ID before checking whether
# it was already delivered. Keep every state they can read by ID strict-
# reader safe; dead letters alone retain queryable repair diagnostics.
exclude = delivery_fields if intent.delivery_state != 'dead_letter' else set()
payload = intent.model_dump(mode='python', exclude=exclude)
return payload
def _intent_with_delivery_attempt(intent: ProactiveIntent, snapshot: Any) -> ProactiveIntent:
try:
return delivery_attempts.intent_with_delivery_attempt(intent, snapshot)
except delivery_attempts.ChatFirstMalformedDeliveryAttempt as error:
raise ChatFirstMalformedDocument('chat-first delivery attempt state is malformed') from error
def _reset_malformed_delivery_attempt(intent: ProactiveIntent, raw: dict[str, Any], *, now: datetime) -> dict[str, Any]:
return delivery_attempts.reset_malformed_delivery_attempt(intent, raw, now=now)
def get_budget_state(
uid: str,
*,
account_generation: int,
now: datetime,
firestore_client: Any = None,
) -> ProactiveBudgetState:
"""Read bounded accounting after the caller passed generation validation."""
client = _db(firestore_client)
_require_current_control(uid, account_generation=account_generation, firestore_client=client)
snapshot = _budget_ref(uid, firestore_client=client).get()
return _budget_from_snapshot(snapshot, account_generation=account_generation, now=now)
def admit_agent_judgment(
uid: str,
*,
continuity_key: str,
subject: ChatFirstSubject,
account_generation: int,
now: datetime,
firestore_client: Any = None,
) -> AgentJudgmentAdmission:
"""Atomically reserve one agent-tier evaluation before any provider call.
This is intentionally separate from ``create_intent``. It makes the
budget a genuine model-cost gate under concurrent wakes while allowing a
declined or failed judgment to release its reservation without consuming a
materialized turn.
"""
client = _db(firestore_client)
intent_id = proactive_intent_id(
uid,
account_generation=account_generation,
source_key='agent_judgment',
continuity_key=continuity_key,
)
intent_ref = _intent_ref(uid, intent_id, firestore_client=client)
budget_ref = _budget_ref(uid, firestore_client=client)
transaction = client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> AgentJudgmentAdmission:
control_snapshot = _control_ref(uid, firestore_client=client).get(transaction=write_transaction)
_require_control(control_snapshot, uid=uid, account_generation=account_generation)
existing_snapshot = intent_ref.get(transaction=write_transaction)
if existing_snapshot.exists:
existing = _intent_from_snapshot(existing_snapshot)
if (
existing.account_generation != account_generation
or existing.source != 'agent_judgment'
or existing.continuity_key != continuity_key
or existing.subject != subject
):
raise ChatFirstIntentConflictError('agent judgment continuity key was reused')
return AgentJudgmentAdmission(existing_intent=existing, newly_reserved=False)
budget_snapshot = budget_ref.get(transaction=write_transaction)
budget = _budget_from_snapshot(budget_snapshot, account_generation=account_generation, now=now)
if any(reservation.intent_id == intent_id for reservation in budget.reservations):
return AgentJudgmentAdmission(existing_intent=None, newly_reserved=False)
try:
reserved = reserve_budget(budget, intent_id=intent_id, now=now)
except ValueError as exc:
raise ProactiveBudgetExhausted('proactive turn budget exhausted') from exc
write_transaction.set(budget_ref, reserved.model_dump(mode='python'))
return AgentJudgmentAdmission(existing_intent=None, newly_reserved=True)
return apply(transaction)
def release_agent_judgment_admission(
uid: str,
*,
continuity_key: str,
account_generation: int,
now: datetime,
firestore_client: Any = None,
) -> None:
"""Release an unused pre-judge reservation without touching an intent.
An existing intent owns its reservation until the local kernel receipt. A
retry after a provider failure or empty selection therefore remains safe
and idempotent.
"""
client = _db(firestore_client)
intent_id = proactive_intent_id(
uid,
account_generation=account_generation,
source_key='agent_judgment',
continuity_key=continuity_key,
)
intent_ref = _intent_ref(uid, intent_id, firestore_client=client)
budget_ref = _budget_ref(uid, firestore_client=client)
transaction = client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> None:
control_snapshot = _control_ref(uid, firestore_client=client).get(transaction=write_transaction)
_require_control(control_snapshot, uid=uid, account_generation=account_generation)
intent_snapshot = intent_ref.get(transaction=write_transaction)
budget_snapshot = budget_ref.get(transaction=write_transaction)
if intent_snapshot.exists:
return
budget = _budget_from_snapshot(budget_snapshot, account_generation=account_generation, now=now)
reservations = [reservation for reservation in budget.reservations if reservation.intent_id != intent_id]
if len(reservations) == len(budget.reservations):
return
write_transaction.set(
budget_ref, budget.model_copy(update={'reservations': reservations}).model_dump(mode='python')
)
apply(transaction)
def create_intent(
uid: str,
*,
source: ProactiveIntentSource,
continuity_key: str,
subject: ChatFirstSubject | None,
blocks: list[ChatFirstBlockSpec],
account_generation: int,
now: datetime,
firestore_client: Any = None,
) -> tuple[ProactiveIntent, bool]:
"""Idempotently persist an intent and atomically reserve agent-turn budget."""
client = _db(firestore_client)
intent_id = proactive_intent_id(
uid,
account_generation=account_generation,
source_key=source,
continuity_key=continuity_key,
)
intent = ProactiveIntent(
intent_id=intent_id,
continuity_key=continuity_key,
account_generation=account_generation,
source=source,
subject=subject,
blocks=blocks,
created_at=now,
)
intent_ref = _intent_ref(uid, intent_id, firestore_client=client)
dead_ref = _dead_letter_ref(uid, intent_id, firestore_client=client)
budget_ref = _budget_ref(uid, firestore_client=client)
transaction = client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> tuple[ProactiveIntent, bool]:
control_snapshot = _control_ref(uid, firestore_client=client).get(transaction=write_transaction)
_require_control(control_snapshot, uid=uid, account_generation=account_generation)
existing_snapshot = intent_ref.get(transaction=write_transaction)
dead_snapshot = dead_ref.get(transaction=write_transaction)
budget_snapshot = (
budget_ref.get(transaction=write_transaction)
if intent.consumes_turn_budget and not existing_snapshot.exists and not dead_snapshot.exists
else None
)
if existing_snapshot.exists:
existing = _intent_from_snapshot(existing_snapshot)
if (
existing.account_generation != account_generation
or existing.source != source
or existing.continuity_key != continuity_key
or existing.subject != subject
or existing.blocks != blocks
):
raise ChatFirstIntentConflictError('intent continuity key was reused with different content')
return existing, False
if dead_snapshot.exists:
existing = _intent_from_snapshot(dead_snapshot)
if (
existing.account_generation != account_generation
or existing.source != source
or existing.continuity_key != continuity_key
or existing.subject != subject
or existing.blocks != blocks
):
raise ChatFirstIntentConflictError('intent continuity key was reused with different content')
return existing, False
reserved: ProactiveBudgetState | None = None
if intent.consumes_turn_budget:
assert budget_snapshot is not None
budget = _budget_from_snapshot(budget_snapshot, account_generation=account_generation, now=now)
try:
reserved = reserve_budget(budget, intent_id=intent_id, now=now)
except ValueError as exc:
raise ProactiveBudgetExhausted('proactive turn budget exhausted') from exc
write_transaction.set(intent_ref, _intent_payload(intent))
if reserved is not None:
write_transaction.set(budget_ref, reserved.model_dump(mode='python'))
return intent, True
return apply(transaction)
def get_or_create_cold_start_intent(
uid: str,
*,
source: ProactiveIntentSource,
continuity_key: str,
subject: ChatFirstSubject | None,
blocks: list[ChatFirstBlockSpec],
account_generation: int,
now: datetime,
firestore_client: Any = None,
) -> tuple[ProactiveIntent, bool]:
"""Persist exactly one generation-bound cold-start intent.
Cold-start richness is sampled only for the first writer. The stable ID is
deliberately independent of the selected rich/sparse source so a retry
after canonical data changes returns the original ready intent rather than
producing a second first-run experience.
"""
if source not in {'cold_start_rich', 'cold_start_sparse'}:
raise ValueError('cold-start intents require a cold-start source')
client = _db(firestore_client)
intent_id = proactive_intent_id(
uid,
account_generation=account_generation,
source_key='cold_start',
continuity_key=continuity_key,
)
intent = ProactiveIntent(
intent_id=intent_id,
continuity_key=continuity_key,
account_generation=account_generation,
source=source,
subject=subject,
blocks=blocks,
delivery_state='pending_kernel_receipt',
created_at=now,
)
intent_ref = _intent_ref(uid, intent_id, firestore_client=client)
transaction = client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> tuple[ProactiveIntent, bool]:
control_snapshot = _control_ref(uid, firestore_client=client).get(transaction=write_transaction)
_require_control(control_snapshot, uid=uid, account_generation=account_generation)
existing_snapshot = intent_ref.get(transaction=write_transaction)
if existing_snapshot.exists:
existing = _intent_from_snapshot(existing_snapshot)
if (
existing.account_generation != account_generation
or existing.continuity_key != continuity_key
or existing.source not in {'cold_start_rich', 'cold_start_sparse'}
):
raise ChatFirstIntentConflictError('cold-start continuity key was reused')
return existing, False
write_transaction.set(intent_ref, _intent_payload(intent))
return intent, True
return apply(transaction)
def has_cold_start_intent_created_on(
uid: str,
*,
account_generation: int,
date_value: date,
firestore_client: Any = None,
) -> bool:
"""Whether this generation already used today's deterministic opener slot."""
client = _db(firestore_client)
_require_current_control(uid, account_generation=account_generation, firestore_client=client)
collection = _user_ref(uid, firestore_client=client).collection(INTENTS_COLLECTION)
for snapshot in collection.stream():
intent = _intent_from_snapshot(snapshot)
if intent.account_generation != account_generation:
continue
if intent.source not in {'cold_start_rich', 'cold_start_sparse'}:
continue
if intent.created_at.date() == date_value:
return True
return False
def acknowledge_sparse_cold_start_sequence_terminal(
uid: str,
*,
sequence_id: str,
receipt_id: str,
terminal_state: ColdStartSequenceTerminalState,
account_generation: int,
now: datetime,
firestore_client: Any = None,
) -> ProactiveIntent:
"""Accept one local-journal terminal receipt for the sparse sequence.
The receipt is attached to the original cold-start intent so it cannot
become a client/operator completion flag. A sparse sequence remains active
through the crash window before its initial materialization receipt reaches
the server, then releases agent-tier judgment only after this terminal
journal fact is durably acknowledged.
"""
expected_sequence_id = f'cold-start:{account_generation}'
if sequence_id != expected_sequence_id:
raise ChatFirstIntentConflictError('cold-start terminal sequence does not match generation')
client = _db(firestore_client)
intent_id = proactive_intent_id(
uid,
account_generation=account_generation,
source_key='cold_start',
continuity_key=sequence_id,
)
intent_ref = _intent_ref(uid, intent_id, firestore_client=client)
transaction = client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> ProactiveIntent:
control_snapshot = _control_ref(uid, firestore_client=client).get(transaction=write_transaction)
_require_control(control_snapshot, uid=uid, account_generation=account_generation)
snapshot = intent_ref.get(transaction=write_transaction)
if not snapshot.exists:
raise ProactiveIntentNotReady('cold-start intent is not ready')
intent = _intent_from_snapshot(snapshot)
if (
intent.account_generation != account_generation
or intent.source != 'cold_start_sparse'
or intent.subject != ChatFirstSubject(kind='cold_start', id=sequence_id)
or intent.delivery_state != 'delivered'
or intent.materialization_receipt_id is None
):
raise ProactiveIntentNotReady('cold-start sequence is not ready for terminal acknowledgement')
if intent.cold_start_sequence_terminal_receipt_id is not None:
if (
intent.cold_start_sequence_terminal_receipt_id != receipt_id
or intent.cold_start_sequence_terminal_state != terminal_state
):
raise ChatFirstIntentConflictError('cold-start sequence was already terminalized differently')
return intent
terminalized = intent.model_copy(
update={
'cold_start_sequence_terminal_state': terminal_state,
'cold_start_sequence_terminal_receipt_id': receipt_id,
}
)
write_transaction.set(intent_ref, _intent_payload(terminalized))
return terminalized
return apply(transaction)
def has_active_sparse_cold_start_sequence(
uid: str,
*,
account_generation: int,
firestore_client: Any = None,
) -> bool:
"""Whether a sparse local-journal sequence can still own the Chat tail."""
client = _db(firestore_client)
_require_current_control(uid, account_generation=account_generation, firestore_client=client)
collection = _user_ref(uid, firestore_client=client).collection(INTENTS_COLLECTION)
for snapshot in collection.stream():
intent = _intent_from_snapshot(snapshot)
if intent.account_generation != account_generation or intent.source != 'cold_start_sparse':
continue
if intent.cold_start_sequence_terminal_receipt_id is None:
return True
return False
def _stable_chat_first_turn_id(intent_id: str) -> str:
return f'turn_cfi_{hashlib.sha256(intent_id.encode()).hexdigest()[:24]}'
def _synthetic_reconciliation_receipt_id(intent_id: str) -> str:
return f'{_SYNTHETIC_RECONCILIATION_RECEIPT_PREFIX}{hashlib.sha256(intent_id.encode()).hexdigest()[:24]}'
def _message_has_intent_identity(uid: str, intent_id: str, *, firestore_client: Any) -> bool:
"""Point-read the stable chat row and verify its embedded intent identity."""
snapshot = (
_user_ref(uid, firestore_client=firestore_client)
.collection('messages')
.document(_stable_chat_first_turn_id(intent_id))
.get()
)
if not snapshot.exists:
return False
metadata = (snapshot.to_dict() or {}).get('metadata')
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except (TypeError, ValueError):
return False
return isinstance(metadata, dict) and metadata.get('chatFirstIntentId') == intent_id
def _is_capture_receipt(intent: ProactiveIntent) -> bool:
"""The plain "your conversation is ready" receipt, not a meeting-notes intent.
A desktop meeting carries a ``conversationLink`` under the same source and
keeps its own delivery contract; only the bare ``captureLink`` receipt is
the per-conversation notice that collapses.
"""
return intent.source == 'capture_arrival' and all(block.type == 'captureLink' for block in intent.blocks)
def _fetch_priority(intent: ProactiveIntent) -> int:
if intent.source == 'daily_opener' or any(block.type == 'conversationLink' for block in intent.blocks):
return 0
if _is_capture_receipt(intent):
return 2
return 1
def _advance_fetched_intent(
uid: str,
intent_id: str,
*,
account_generation: int,
now: datetime,
reconcile: bool,
firestore_client: Any,
) -> tuple[ProactiveIntent | None, IntentLifecycleEvent | None]:
intent_ref = _intent_ref(uid, intent_id, firestore_client=firestore_client)
attempt_ref = _delivery_attempt_ref(uid, intent_id, firestore_client=firestore_client)
transaction = firestore_client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> tuple[ProactiveIntent | None, IntentLifecycleEvent | None]:
snapshot = intent_ref.get(transaction=write_transaction)
if not snapshot.exists:
return None, None
intent = _intent_from_snapshot(snapshot)
attempt_snapshot = attempt_ref.get(transaction=write_transaction)
malformed_attempt = False
try:
intent = _intent_with_delivery_attempt(intent, attempt_snapshot)
except ChatFirstMalformedDocument:
# The sibling is derived delivery bookkeeping. Repair it in this
# transaction instead of allowing corrupt derived state to hide a
# valid intent indefinitely.
malformed_attempt = True
if intent.account_generation != account_generation or intent.delivery_state not in {
'ready',
'pending_kernel_receipt',
}:
return None, None
attempt_update = (
_reset_malformed_delivery_attempt(intent, attempt_snapshot.to_dict() or {}, now=now)
if malformed_attempt
else {'fetch_count': intent.fetch_count + 1, 'last_fetched_at': now}
)
fetch_count = cast(int, attempt_update['fetch_count'])
requeue_count = cast(int, attempt_update.get('requeue_count', intent.requeue_count))
common = {'fetch_count': fetch_count, 'last_fetched_at': now}
if reconcile:
budget_ref = _budget_ref(uid, firestore_client=firestore_client)
budget_snapshot = budget_ref.get(transaction=write_transaction) if intent.consumes_turn_budget else None
delivered = intent.model_copy(
update={
**common,
'delivery_state': 'delivered',
'delivered_at': now,
'materialization_receipt_id': _synthetic_reconciliation_receipt_id(intent.intent_id),
}
)
if intent.consumes_turn_budget:
assert budget_snapshot is not None
budget = _budget_from_snapshot(budget_snapshot, account_generation=account_generation, now=now)
write_transaction.set(
budget_ref, account_materialization(budget, intent_id=intent_id, now=now).model_dump(mode='python')
)
write_transaction.set(intent_ref, _intent_payload(delivered))
write_transaction.set(attempt_ref, attempt_update, merge=not malformed_attempt)
return None, IntentLifecycleEvent('reconciled', intent.source, 'existing_chat_row')
if fetch_count >= UNACKNOWLEDGED_FETCH_BUDGET:
dead_lettered = intent.model_copy(
update={
**attempt_update,
'delivery_state': 'dead_letter',
'dead_letter_reason': (
TRANSIENT_DEATH_AFTER_REQUEUE_REASON if requeue_count > 0 else UNACKNOWLEDGED_DEAD_LETTER_REASON
),
}
)
delivery_attempts.move_to_dead_letters(
write_transaction,
intent_ref_value=intent_ref,
dead_letter_ref_value=_dead_letter_ref(uid, intent_id, firestore_client=firestore_client),
intent=dead_lettered,
terminal_at=now,
)
write_transaction.set(attempt_ref, attempt_update, merge=not malformed_attempt)
return None, IntentLifecycleEvent(
'dead_letter', intent.source, dead_lettered.dead_letter_reason or 'unknown'
)
fetched = intent.model_copy(update=common)
write_transaction.set(attempt_ref, attempt_update, merge=not malformed_attempt)
event = (
IntentLifecycleEvent('malformed_attempt_reset', intent.source, 'malformed_document')
if malformed_attempt
else None
)
return fetched, event
return apply(transaction)
def _dead_letter_malformed_intent(
uid: str,
intent_id: str,
*,
account_generation: int,
firestore_client: Any,
) -> None:
"""Terminalize one still-active malformed row without racing a newer writer."""
intent_ref = _intent_ref(uid, intent_id, firestore_client=firestore_client)
transaction = firestore_client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> None:
snapshot = intent_ref.get(transaction=write_transaction)
if not snapshot.exists:
return
raw = snapshot.to_dict() or {}
if raw.get('account_generation') != account_generation or raw.get('delivery_state') not in {
'ready',
'pending_kernel_receipt',
}:
return
try:
_intent_from_snapshot(snapshot)
except ChatFirstMalformedDocument:
dead_payload = {
**raw,
'intent_id': raw.get('intent_id') or intent_id,
'delivery_state': 'dead_letter',
'dead_letter_reason': 'malformed_document',
}
write_transaction.set(_dead_letter_ref(uid, intent_id, firestore_client=firestore_client), dead_payload)
write_transaction.delete(intent_ref)
apply(transaction)
def _requeue_transient_dead_letter(
uid: str,
intent_id: str,
*,
account_generation: int,
now: datetime,
firestore_client: Any,
) -> ProactiveIntent | None:
try:
return delivery_attempts.requeue_transient_dead_letter(
uid,
intent_id,
account_generation=account_generation,
now=now,
firestore_client=firestore_client,
)
except delivery_attempts.ChatFirstMalformedDeliveryAttempt as error:
raise ChatFirstMalformedDocument('chat-first dead letter is malformed') from error
def _retire_capture_receipt(
uid: str,
intent_id: str,
*,
account_generation: int,
reason: str,
now: datetime,
firestore_client: Any,
) -> bool:
"""Terminalize one capture receipt that was never handed to a kernel.
Returns whether the row was retired. Everything is decided from a re-read
inside the transaction, because the caller's copy comes from a collection
scan that cannot see the sibling delivery-attempt document:
* A fetch does not change ``delivery_state`` for a normal intent -- it only
increments ``fetch_count`` on that sibling -- so ``ready`` alone does not
mean undelivered. A receipt some kernel is already holding is refused
here and left to the unacknowledged-fetch budget, which is the existing
owner of a delivery that never came back.
* The terminal record is written from the hydrated intent, so the dead
letter keeps the fetch and deferral history rather than defaults.
"""
intent_ref = _intent_ref(uid, intent_id, firestore_client=firestore_client)
attempt_ref = _delivery_attempt_ref(uid, intent_id, firestore_client=firestore_client)
transaction = firestore_client.transaction()
@firestore.transactional
def apply(write_transaction: Any) -> bool:
snapshot = intent_ref.get(transaction=write_transaction)
if not snapshot.exists:
return False
try:
intent = _intent_with_delivery_attempt(
_intent_from_snapshot(snapshot), attempt_ref.get(transaction=write_transaction)
)
except ChatFirstMalformedDocument:
# The malformed sweep in this same fetch owns that row.
return False
if (
intent.account_generation != account_generation
or intent.delivery_state != 'ready'
or not _is_capture_receipt(intent)
or intent.fetch_count > 0
):
return False
delivery_attempts.move_to_dead_letters(
write_transaction,
intent_ref_value=intent_ref,
dead_letter_ref_value=_dead_letter_ref(uid, intent_id, firestore_client=firestore_client),
intent=intent.model_copy(update={'delivery_state': 'dead_letter', 'dead_letter_reason': reason}),
terminal_at=now,
)
return True
return apply(transaction)
def fetch_ready_intent_batch(
uid: str,
*,
account_generation: int,
limit: int = 8,
exclude_block_types: set[str] | frozenset[str] | None = None,
deferred_intent_ids: set[str] | frozenset[str] | None = None,
now: datetime | None = None,
firestore_client: Any = None,
) -> ReadyIntentBatch:
"""Fetch a priority batch while bounding poison retries and reconciling stable rows."""
client = _db(firestore_client)
fetched_at = now or datetime.now(timezone.utc)
_require_current_control(uid, account_generation=account_generation, firestore_client=client)
collection = _user_ref(uid, firestore_client=client).collection(INTENTS_COLLECTION)
lifecycle_events: list[IntentLifecycleEvent] = []
if delivery_attempts.repair_transient_dead_letters(
uid,
account_generation=account_generation,
limit=limit,
now=fetched_at,
firestore_client=client,
requeue=_requeue_transient_dead_letter,
):
lifecycle_events.append(IntentLifecycleEvent('repair_scan_failed', 'materialization', 'google_api_error'))
# Push the delivery-state filter to Firestore so delivered historical rows
# are never transferred for a foreground materialization. The caller only
# ever needs ready or pending-receipt intents, which are bounded; the full
# collection otherwise grows with account age.
query = collection.where(filter=FieldFilter('delivery_state', 'in', ['ready', 'pending_kernel_receipt']))
candidates: list[ProactiveIntent] = []
malformed_intent_ids: list[str] = []
capture_receipts: list[ProactiveIntent] = []
for snapshot in query.stream():
try:
intent = _intent_from_snapshot(snapshot)
except ChatFirstMalformedDocument:
malformed_intent_ids.append(snapshot.id)
continue
if intent.account_generation != account_generation:
continue
# Apply compatibility filtering before the delivery window is bounded.
# Legacy clients cannot acknowledge newer block types, so letting those
# rows consume the first ``limit`` results would permanently starve the
# legacy-compatible intents behind them.
if exclude_block_types and any(block.type in exclude_block_types for block in intent.blocks):
continue
# A receipt this device explicitly deferred keeps its own contract and
# is re-offered below rather than collapsed against its siblings.
if (
intent.delivery_state == 'ready'
and _is_capture_receipt(intent)
and not (deferred_intent_ids and intent.intent_id in deferred_intent_ids)
):
capture_receipts.append(intent)
continue
candidates.append(intent)
# Keep at most the newest live receipt and retire the rest.
capture_receipts.sort(key=lambda intent: (intent.created_at, intent.intent_id))
retiring_captures: list[tuple[ProactiveIntent, str]] = [
(intent, SUPERSEDED_CAPTURE_DEAD_LETTER_REASON) for intent in capture_receipts[:-1]
]
deliverable_capture: ProactiveIntent | None = None
if capture_receipts:
newest = capture_receipts[-1]
if fetched_at - newest.created_at > CAPTURE_RECEIPT_DELIVERY_WINDOW:
retiring_captures.append((newest, STALE_CAPTURE_DEAD_LETTER_REASON))
else:
deliverable_capture = newest
ready: list[ProactiveIntent] = []
candidate_scan_limit = FETCH_CANDIDATE_SCAN_MULTIPLIER * limit
# Bounded per poll so a long backlog can never make one fetch linear in its
# size. Whatever is left over is excluded from this response either way, so
# it costs nothing to retire it on a later poll instead.
for intent, reason in retiring_captures[:candidate_scan_limit]:
try:
retired = _retire_capture_receipt(
uid,
intent.intent_id,
account_generation=account_generation,
reason=reason,
now=fetched_at,
firestore_client=client,
)