6Typed event factory module — M2.1 (event substrate).
10Provide one keyword-only factory function per ``EventKind`` (spec 07 §3.2,
1120 kinds) that builds the matching payload model and wraps it in a fully
12validated ``option_c_agent_contracts.TurnEvent`` envelope. The factories give
13call sites ergonomics without inventing a parallel type system: every factory
14returns a plain ``TurnEvent`` (ADR-2 of plan 095).
18- Expose one keyword-only ``make_*`` factory per ``EventKind`` that builds the
19 matching payload model and a validated ``TurnEvent`` envelope.
20- Expose ``EVENT_FACTORIES`` (kind -> factory) and ``new_event_id()``.
21- Stay structure-only: no file I/O, no ``run_turn()`` instrumentation, no
22 session-identity binding (those are M2.2-M2.5).
24What this module is NOT
25-----------------------
26No file I/O, no logging beyond the L4 diagnostics marker, no ``run_turn()``
27instrumentation, no session-identity binding. Callers pass ``session_id`` as an
28explicit string; ``TurnEvent.session_id`` is non-optional in the contract and
29M2.5 will plumb the real value through (until then callers pass a placeholder
30such as ``"unbound"``). The choice is pushed to the caller so the factory
31signatures stay honest (plan 095 §9 Q1).
37 L2 — lifecycle events (none emitted here)
38 L3 — semantic details (none emitted here)
39 L4 — module-load coverage marker (silent at default verbosity)
43- Every returned object is a frozen ``option_c_agent_contracts.TurnEvent``;
44 this module never subclasses contract models (plan 095 ADR-2).
45- ``parent_event_id`` is mandatory for every kind except ``session_started``
46 (enforced by the contract's envelope validator, not duplicated here).
47- Module-level L4 ``_dbg`` is a diagnostics-coverage marker only.
51- oc_diagnostics (structured debug logging via _dbg)
52- option_c_agent_contracts (TurnEvent envelope, EventKind, payload models)
53- option_c_agent_contracts.events (the payload model each factory builds for its event kind)
55from __future__
import annotations
58from typing
import Any, Callable, Mapping, Optional
60from oc_diagnostics
import _dbg
66from option_c_agent_contracts
import (
71from option_c_agent_contracts.events
import (
72 AssistantMessagePayload,
74 CompactionCompletedPayload,
75 CompactionStartedPayload,
79 PermissionDecisionPayload,
80 PromptRenderedPayload,
81 ProviderBudgetConsumedPayload,
82 ProviderRateLimitedPayload,
83 ProviderRetriedPayload,
85 SessionStartedPayload,
86 SubagentCompletedPayload,
87 SubagentStartedPayload,
88 ToolCallCompletedPayload,
89 ToolCallProgressPayload,
90 ToolCallProposedPayload,
91 ToolCallStartedPayload,
92 UserInputReceivedPayload,
95_dbg(
"OC_CHAT.MAIN",
"events",
"module", 4)
99 """Return a fresh UUID4 string for use as a ``TurnEvent.event_id``."""
100 return str(uuid.uuid4())
108 parent_event_id: Optional[str],
111 turn_id: Optional[str] =
None,
112 tool_call_id: Optional[str] =
None,
113 permission_decision_id: Optional[str] =
None,
114 provenance: Optional[Provenance] =
None,
115 prompt_version: Optional[str] =
None,
117 """Assemble + validate a ``TurnEvent`` envelope around ``payload``.
119 Defaulting ``provenance`` to a *fresh* ``Provenance()`` per call (never a
120 shared module-level instance) matches the contract field's
121 ``default_factory`` and keeps factory outputs independent.
125 parent_event_id=parent_event_id,
126 session_id=session_id,
128 tool_call_id=tool_call_id,
129 permission_decision_id=permission_decision_id,
133 provenance=provenance
if provenance
is not None else Provenance(),
134 prompt_version=prompt_version,
142 parent_event_id: Optional[str] =
None,
146 provenance: Optional[Provenance] =
None,
147 prompt_version: Optional[str] =
None,
149 """The session root. ``parent_event_id`` defaults to ``None`` (only kind allowed to be a root)."""
152 SessionStartedPayload(agent_role=agent_role),
154 parent_event_id=parent_event_id,
155 session_id=session_id,
157 provenance=provenance,
158 prompt_version=prompt_version,
165 parent_event_id: str,
169 provenance: Optional[Provenance] =
None,
170 prompt_version: Optional[str] =
None,
174 SessionEndedPayload(outcome=outcome),
176 parent_event_id=parent_event_id,
177 session_id=session_id,
179 provenance=provenance,
180 prompt_version=prompt_version,
188 parent_event_id: str,
192 turn_id: Optional[str] =
None,
193 provenance: Optional[Provenance] =
None,
194 prompt_version: Optional[str] =
None,
197 "user_input_received",
198 UserInputReceivedPayload(text=text),
200 parent_event_id=parent_event_id,
201 session_id=session_id,
204 provenance=provenance,
205 prompt_version=prompt_version,
212 parent_event_id: str,
216 is_final: bool =
True,
217 turn_id: Optional[str] =
None,
218 provenance: Optional[Provenance] =
None,
219 prompt_version: Optional[str] =
None,
223 AssistantMessagePayload(text=text, is_final=is_final),
225 parent_event_id=parent_event_id,
226 session_id=session_id,
229 provenance=provenance,
230 prompt_version=prompt_version,
238 parent_event_id: str,
242 layer_ids: Optional[list[str]] =
None,
243 layer_hashes: Optional[dict[str, str]] =
None,
244 turn_id: Optional[str] =
None,
245 provenance: Optional[Provenance] =
None,
249 PromptRenderedPayload(
250 prompt_version=prompt_version,
251 layer_ids=list(layer_ids)
if layer_ids
is not None else [],
252 layer_hashes=dict(layer_hashes)
if layer_hashes
is not None else {},
255 parent_event_id=parent_event_id,
256 session_id=session_id,
259 provenance=provenance,
260 prompt_version=prompt_version,
267 parent_event_id: str,
271 retry_after_seconds: Optional[float] =
None,
272 turn_id: Optional[str] =
None,
273 provenance: Optional[Provenance] =
None,
274 prompt_version: Optional[str] =
None,
277 "provider_rate_limited",
278 ProviderRateLimitedPayload(
279 provider_name=provider_name,
280 retry_after_seconds=retry_after_seconds,
283 parent_event_id=parent_event_id,
284 session_id=session_id,
287 provenance=provenance,
288 prompt_version=prompt_version,
295 parent_event_id: str,
300 turn_id: Optional[str] =
None,
301 provenance: Optional[Provenance] =
None,
302 prompt_version: Optional[str] =
None,
306 ProviderRetriedPayload(provider_name=provider_name, attempt=attempt),
308 parent_event_id=parent_event_id,
309 session_id=session_id,
312 provenance=provenance,
313 prompt_version=prompt_version,
320 parent_event_id: str,
324 input_tokens: int = 0,
325 output_tokens: int = 0,
326 cost_usd: Optional[float] =
None,
327 model: Optional[str] =
None,
328 cache_read_tokens: Optional[int] =
None,
329 cache_miss_tokens: Optional[int] =
None,
330 priced_at: Optional[str] =
None,
331 rate_effective_from: Optional[str] =
None,
332 rate_verified_at: Optional[str] =
None,
333 turn_id: Optional[str] =
None,
334 provenance: Optional[Provenance] =
None,
335 prompt_version: Optional[str] =
None,
337 """Plan 203: ``cost_usd=None`` means UNKNOWN, not free.
339 The default is ``None`` rather than ``0.0`` so a caller that cannot price
340 a sample gets the truthful encoding by omission instead of manufacturing
341 spend the projection would sum as real.
344 "provider_budget_consumed",
345 ProviderBudgetConsumedPayload(
346 provider_name=provider_name,
347 input_tokens=input_tokens,
348 output_tokens=output_tokens,
351 cache_read_tokens=cache_read_tokens,
352 cache_miss_tokens=cache_miss_tokens,
354 rate_effective_from=rate_effective_from,
355 rate_verified_at=rate_verified_at,
358 parent_event_id=parent_event_id,
359 session_id=session_id,
362 provenance=provenance,
363 prompt_version=prompt_version,
371 parent_event_id: str,
375 requested_args: Optional[dict[str, Any]] =
None,
376 turn_id: Optional[str] =
None,
377 provenance: Optional[Provenance] =
None,
378 prompt_version: Optional[str] =
None,
381 "tool_call_proposed",
382 ToolCallProposedPayload(
384 requested_args=dict(requested_args)
if requested_args
is not None else {},
387 parent_event_id=parent_event_id,
388 session_id=session_id,
391 provenance=provenance,
392 prompt_version=prompt_version,
399 parent_event_id: str,
402 permission_decision_id: str,
405 turn_id: Optional[str] =
None,
406 provenance: Optional[Provenance] =
None,
407 prompt_version: Optional[str] =
None,
411 tool_call_id: Optional[str] =
None,
412 policy_version: Optional[str] =
None,
413 risk_class: Optional[str] =
None,
414 final_outcome: Optional[str] =
None,
415 input_hash: Optional[str] =
None,
416 permission_request_id: Optional[str] =
None,
417 canonical_request_hash: Optional[str] =
None,
418 grant_fingerprint: Optional[str] =
None,
419 envelope_fingerprint: Optional[str] =
None,
420 grant_key_fingerprint: Optional[str] =
None,
421 authorization_mode: Optional[str] =
None,
422 denial_reason_code: Optional[str] =
None,
424 """Sets ``permission_decision_id`` on both the payload and the envelope.
426 The M8.1 audit fields are threaded onto the payload when supplied; omitting
427 them yields the legacy 3-field shape (wire-compat).
430 "permission_decision",
431 PermissionDecisionPayload(
432 permission_decision_id=permission_decision_id,
434 decided_by=decided_by,
435 tool_call_id=tool_call_id,
436 policy_version=policy_version,
437 risk_class=risk_class,
438 final_outcome=final_outcome,
439 input_hash=input_hash,
440 permission_request_id=permission_request_id,
441 canonical_request_hash=canonical_request_hash,
442 grant_fingerprint=grant_fingerprint,
443 envelope_fingerprint=envelope_fingerprint,
444 grant_key_fingerprint=grant_key_fingerprint,
445 authorization_mode=authorization_mode,
446 denial_reason_code=denial_reason_code,
449 parent_event_id=parent_event_id,
450 session_id=session_id,
453 permission_decision_id=permission_decision_id,
454 provenance=provenance,
455 prompt_version=prompt_version,
462 parent_event_id: str,
467 turn_id: Optional[str] =
None,
468 provenance: Optional[Provenance] =
None,
469 prompt_version: Optional[str] =
None,
471 """Sets ``tool_call_id`` on both the payload and the envelope."""
474 ToolCallStartedPayload(tool_call_id=tool_call_id, tool_name=tool_name),
476 parent_event_id=parent_event_id,
477 session_id=session_id,
480 tool_call_id=tool_call_id,
481 provenance=provenance,
482 prompt_version=prompt_version,
489 parent_event_id: str,
494 turn_id: Optional[str] =
None,
495 provenance: Optional[Provenance] =
None,
496 prompt_version: Optional[str] =
None,
498 """Sets ``tool_call_id`` on both the payload and the envelope."""
500 "tool_call_progress",
501 ToolCallProgressPayload(tool_call_id=tool_call_id, message=message),
503 parent_event_id=parent_event_id,
504 session_id=session_id,
507 tool_call_id=tool_call_id,
508 provenance=provenance,
509 prompt_version=prompt_version,
516 parent_event_id: str,
521 result_artifact_id: Optional[str] =
None,
522 error_summary: Optional[str] =
None,
523 turn_id: Optional[str] =
None,
524 provenance: Optional[Provenance] =
None,
525 prompt_version: Optional[str] =
None,
527 """Sets ``tool_call_id`` on both the payload and the envelope.
529 Plan 203 (audit G10): *error_summary* carries WHY a call failed. The
530 contract bounds it at ``ERROR_SUMMARY_MAX_CHARS`` and truncates rather
531 than rejects, so a caller may pass the failure text it has in hand
532 without pre-trimming it.
535 "tool_call_completed",
536 ToolCallCompletedPayload(
537 tool_call_id=tool_call_id,
539 result_artifact_id=result_artifact_id,
540 error_summary=error_summary,
543 parent_event_id=parent_event_id,
544 session_id=session_id,
547 tool_call_id=tool_call_id,
548 provenance=provenance,
549 prompt_version=prompt_version,
557 parent_event_id: str,
560 subagent_session_id: str,
561 turn_id: Optional[str] =
None,
562 provenance: Optional[Provenance] =
None,
563 prompt_version: Optional[str] =
None,
567 SubagentStartedPayload(subagent_session_id=subagent_session_id),
569 parent_event_id=parent_event_id,
570 session_id=session_id,
573 provenance=provenance,
574 prompt_version=prompt_version,
581 parent_event_id: str,
584 subagent_session_id: str,
586 turn_id: Optional[str] =
None,
587 provenance: Optional[Provenance] =
None,
588 prompt_version: Optional[str] =
None,
591 "subagent_completed",
592 SubagentCompletedPayload(
593 subagent_session_id=subagent_session_id, outcome=outcome
596 parent_event_id=parent_event_id,
597 session_id=session_id,
600 provenance=provenance,
601 prompt_version=prompt_version,
609 parent_event_id: str,
613 turn_id: Optional[str] =
None,
614 provenance: Optional[Provenance] =
None,
615 prompt_version: Optional[str] =
None,
618 "compaction_started",
619 CompactionStartedPayload(reason=reason),
621 parent_event_id=parent_event_id,
622 session_id=session_id,
625 provenance=provenance,
626 prompt_version=prompt_version,
633 parent_event_id: str,
636 summary_artifact_id: Optional[str] =
None,
637 turn_id: Optional[str] =
None,
638 provenance: Optional[Provenance] =
None,
639 prompt_version: Optional[str] =
None,
642 "compaction_completed",
643 CompactionCompletedPayload(summary_artifact_id=summary_artifact_id),
645 parent_event_id=parent_event_id,
646 session_id=session_id,
649 provenance=provenance,
650 prompt_version=prompt_version,
657 parent_event_id: str,
661 turn_id: Optional[str] =
None,
662 provenance: Optional[Provenance] =
None,
663 prompt_version: Optional[str] =
None,
667 BranchSummaryPayload(summary=summary),
669 parent_event_id=parent_event_id,
670 session_id=session_id,
673 provenance=provenance,
674 prompt_version=prompt_version,
682 parent_event_id: str,
686 turn_id: Optional[str] =
None,
687 provenance: Optional[Provenance] =
None,
688 prompt_version: Optional[str] =
None,
692 ErrorPayload(error_summary=error_summary),
694 parent_event_id=parent_event_id,
695 session_id=session_id,
698 provenance=provenance,
699 prompt_version=prompt_version,
706 parent_event_id: str,
711 turn_id: Optional[str] =
None,
712 provenance: Optional[Provenance] =
None,
713 prompt_version: Optional[str] =
None,
717 HookExecutedPayload(hook_name=hook_name, exit_code=exit_code),
719 parent_event_id=parent_event_id,
720 session_id=session_id,
723 provenance=provenance,
724 prompt_version=prompt_version,
731 parent_event_id: str,
738 subject: Optional[str] =
None,
739 in_reply_to: Optional[str] =
None,
741 turn_id: Optional[str] =
None,
742 provenance: Optional[Provenance] =
None,
743 prompt_version: Optional[str] =
None,
745 """Plan 259: one addressed hub message, as telemetry.
747 Published by ``core/hub_bus.py``'s carrier when a bus was injected, so
748 a session's own event writer records the fleet traffic that crossed
749 it. The payload carries routing and a one-line summary and nothing
750 else: the hub's durable log is the record of WHAT was said, and this
751 event is only the record that something was said.
756 message_id=message_id,
761 in_reply_to=in_reply_to,
765 parent_event_id=parent_event_id,
766 session_id=session_id,
769 provenance=provenance,
770 prompt_version=prompt_version,
778EVENT_FACTORIES: Mapping[EventKind, Callable[..., TurnEvent]] = {
779 "session_started": make_session_started,
780 "session_ended": make_session_ended,
781 "user_input_received": make_user_input_received,
782 "assistant_message": make_assistant_message,
783 "prompt_rendered": make_prompt_rendered,
784 "provider_rate_limited": make_provider_rate_limited,
785 "provider_retried": make_provider_retried,
786 "provider_budget_consumed": make_provider_budget_consumed,
787 "tool_call_proposed": make_tool_call_proposed,
788 "permission_decision": make_permission_decision,
789 "tool_call_started": make_tool_call_started,
790 "tool_call_progress": make_tool_call_progress,
791 "tool_call_completed": make_tool_call_completed,
792 "subagent_started": make_subagent_started,
793 "subagent_completed": make_subagent_completed,
794 "compaction_started": make_compaction_started,
795 "compaction_completed": make_compaction_completed,
796 "branch_summary": make_branch_summary,
798 "hook_executed": make_hook_executed,
799 "hub_message": make_hub_message,
805 "make_session_started",
806 "make_session_ended",
807 "make_user_input_received",
808 "make_assistant_message",
809 "make_prompt_rendered",
810 "make_provider_rate_limited",
811 "make_provider_retried",
812 "make_provider_budget_consumed",
813 "make_tool_call_proposed",
814 "make_permission_decision",
815 "make_tool_call_started",
816 "make_tool_call_progress",
817 "make_tool_call_completed",
818 "make_subagent_started",
819 "make_subagent_completed",
820 "make_compaction_started",
821 "make_compaction_completed",
822 "make_branch_summary",
824 "make_hook_executed",
TurnEvent make_session_ended(*, str event_id, str parent_event_id, str session_id, str ts, str outcome, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_assistant_message(*, str event_id, str parent_event_id, str session_id, str ts, str text, bool is_final=True, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_permission_decision(*, str event_id, str parent_event_id, str session_id, str ts, str permission_decision_id, str decision, str decided_by, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None, Optional[str] tool_call_id=None, Optional[str] policy_version=None, Optional[str] risk_class=None, Optional[str] final_outcome=None, Optional[str] input_hash=None, Optional[str] permission_request_id=None, Optional[str] canonical_request_hash=None, Optional[str] grant_fingerprint=None, Optional[str] envelope_fingerprint=None, Optional[str] grant_key_fingerprint=None, Optional[str] authorization_mode=None, Optional[str] denial_reason_code=None)
TurnEvent make_hub_message(*, str event_id, str parent_event_id, str session_id, str ts, str message_id, str hub_kind, str sender, str recipient, Optional[str] subject=None, Optional[str] in_reply_to=None, str summary="", Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_hook_executed(*, str event_id, str parent_event_id, str session_id, str ts, str hook_name, int exit_code=0, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_branch_summary(*, str event_id, str parent_event_id, str session_id, str ts, str summary, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_provider_budget_consumed(*, str event_id, str parent_event_id, str session_id, str ts, str provider_name, int input_tokens=0, int output_tokens=0, Optional[float] cost_usd=None, Optional[str] model=None, Optional[int] cache_read_tokens=None, Optional[int] cache_miss_tokens=None, Optional[str] priced_at=None, Optional[str] rate_effective_from=None, Optional[str] rate_verified_at=None, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_tool_call_progress(*, str event_id, str parent_event_id, str session_id, str ts, str tool_call_id, str message, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_compaction_completed(*, str event_id, str parent_event_id, str session_id, str ts, Optional[str] summary_artifact_id=None, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_provider_retried(*, str event_id, str parent_event_id, str session_id, str ts, str provider_name, int attempt, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent _make(EventKind kind, Any payload, *, str event_id, Optional[str] parent_event_id, str session_id, str ts, Optional[str] turn_id=None, Optional[str] tool_call_id=None, Optional[str] permission_decision_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_subagent_started(*, str event_id, str parent_event_id, str session_id, str ts, str subagent_session_id, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_provider_rate_limited(*, str event_id, str parent_event_id, str session_id, str ts, str provider_name, Optional[float] retry_after_seconds=None, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_error(*, str event_id, str parent_event_id, str session_id, str ts, str error_summary, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_compaction_started(*, str event_id, str parent_event_id, str session_id, str ts, str reason, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_tool_call_completed(*, str event_id, str parent_event_id, str session_id, str ts, str tool_call_id, str outcome, Optional[str] result_artifact_id=None, Optional[str] error_summary=None, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_subagent_completed(*, str event_id, str parent_event_id, str session_id, str ts, str subagent_session_id, str outcome, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_prompt_rendered(*, str event_id, str parent_event_id, str session_id, str ts, str prompt_version, Optional[list[str]] layer_ids=None, Optional[dict[str, str]] layer_hashes=None, Optional[str] turn_id=None, Optional[Provenance] provenance=None)
TurnEvent make_tool_call_started(*, str event_id, str parent_event_id, str session_id, str ts, str tool_call_id, str tool_name, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_tool_call_proposed(*, str event_id, str parent_event_id, str session_id, str ts, str tool_name, Optional[dict[str, Any]] requested_args=None, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_user_input_received(*, str event_id, str parent_event_id, str session_id, str ts, str text, Optional[str] turn_id=None, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)
TurnEvent make_session_started(*, str event_id, Optional[str] parent_event_id=None, str session_id, str ts, str agent_role, Optional[Provenance] provenance=None, Optional[str] prompt_version=None)