Rohan03 commited on
Commit
213ef24
·
verified ·
1 Parent(s): 20f3085

Sprint 1: canonical event schema (PAEvent, EventKind, Visibility)

Browse files
Files changed (1) hide show
  1. purpose_agent/runtime/events.py +190 -0
purpose_agent/runtime/events.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ events.py — Canonical event schema for Purpose Agent v3.0.
3
+
4
+ Every action in the framework emits a PAEvent. Events are:
5
+ - Typed (EventKind enum)
6
+ - Ordered (per-lane sequence numbers)
7
+ - Scoped (visibility: public/internal/debug)
8
+ - Immutable (frozen dataclass)
9
+ - Serializable (to_dict → JSON/JSONL)
10
+
11
+ Safety: reasoning.summary is allowed. Raw hidden chain-of-thought is NOT.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import time
16
+ import uuid
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ from typing import Any
20
+
21
+
22
+ class Visibility(str, Enum):
23
+ """Who can see this event."""
24
+ PUBLIC = "public" # Safe for UI, logs, external consumers
25
+ INTERNAL = "internal" # Framework internals (memory ops, optimization)
26
+ DEBUG = "debug" # Verbose debug info, never in production streams
27
+
28
+
29
+ class EventKind(str, Enum):
30
+ """All possible event types in the Purpose Agent runtime."""
31
+
32
+ # Run lifecycle
33
+ RUN_STARTED = "run.started"
34
+ RUN_FINISHED = "run.finished"
35
+ RUN_ERROR = "run.error"
36
+
37
+ # Agent lifecycle
38
+ AGENT_STARTED = "agent.started"
39
+ AGENT_PROGRESS = "agent.progress"
40
+ AGENT_FINISHED = "agent.finished"
41
+ AGENT_ERROR = "agent.error"
42
+
43
+ # Reasoning (safe summaries only — NO raw chain-of-thought)
44
+ REASONING_SUMMARY = "reasoning.summary"
45
+
46
+ # Model calls
47
+ MODEL_REQUEST = "model.request"
48
+ MODEL_RESPONSE = "model.response"
49
+
50
+ # Text streaming
51
+ TEXT_DELTA = "text.delta"
52
+ TEXT_DONE = "text.done"
53
+
54
+ # Tool execution
55
+ TOOL_STARTED = "tool.started"
56
+ TOOL_ARGS = "tool.args"
57
+ TOOL_RESULT = "tool.result"
58
+ TOOL_ERROR = "tool.error"
59
+
60
+ # State management
61
+ STATE_SNAPSHOT = "state.snapshot"
62
+ STATE_DELTA = "state.delta"
63
+
64
+ # Checkpointing
65
+ CHECKPOINT_SAVED = "checkpoint.saved"
66
+
67
+ # Human-in-the-loop
68
+ HUMAN_APPROVAL_REQUESTED = "human.approval.requested"
69
+ HUMAN_APPROVAL_RECEIVED = "human.approval.received"
70
+
71
+ # Memory lifecycle
72
+ MEMORY_CANDIDATE = "memory.candidate"
73
+ MEMORY_PROMOTED = "memory.promoted"
74
+ MEMORY_ARCHIVED = "memory.archived"
75
+
76
+ # Skill lifecycle
77
+ SKILL_SELECTED = "skill.selected"
78
+ SKILL_UPDATED = "skill.updated"
79
+ SKILL_ROLLBACK = "skill.rollback"
80
+
81
+
82
+ # Events that MUST NOT contain raw chain-of-thought
83
+ _PRIVATE_KINDS = frozenset() # No kind is inherently private; visibility controls this
84
+
85
+ # Events that should always be delivered even under backpressure
86
+ TERMINAL_KINDS = frozenset({
87
+ EventKind.RUN_FINISHED,
88
+ EventKind.RUN_ERROR,
89
+ EventKind.AGENT_FINISHED,
90
+ EventKind.AGENT_ERROR,
91
+ })
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class PAEvent:
96
+ """
97
+ Canonical event emitted by the Purpose Agent runtime.
98
+
99
+ Every observable action produces one or more PAEvents.
100
+ Events are immutable, ordered, and typed.
101
+
102
+ Fields:
103
+ run_id: Unique ID for this execution run
104
+ session_id: Groups related runs (e.g., a conversation)
105
+ lane_id: Identifies parallel execution lane (for swarm/parallel)
106
+ span_id: Unique ID for this specific event
107
+ parent_span_id: Parent event (for nesting: run → agent → tool)
108
+ seq: Monotonically increasing sequence number within a lane
109
+ ts: Unix timestamp
110
+ kind: What happened (EventKind enum)
111
+ visibility: Who should see this (public/internal/debug)
112
+ payload: Event-specific data
113
+ """
114
+ run_id: str
115
+ session_id: str = ""
116
+ lane_id: str = "main"
117
+ span_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
118
+ parent_span_id: str | None = None
119
+ seq: int = 0
120
+ ts: float = field(default_factory=time.time)
121
+ kind: EventKind = EventKind.AGENT_PROGRESS
122
+ visibility: Visibility = Visibility.PUBLIC
123
+ payload: dict[str, Any] = field(default_factory=dict)
124
+
125
+ def to_dict(self) -> dict[str, Any]:
126
+ """Serialize to dictionary (JSON-safe)."""
127
+ return {
128
+ "run_id": self.run_id,
129
+ "session_id": self.session_id,
130
+ "lane_id": self.lane_id,
131
+ "span_id": self.span_id,
132
+ "parent_span_id": self.parent_span_id,
133
+ "seq": self.seq,
134
+ "ts": self.ts,
135
+ "kind": self.kind.value,
136
+ "visibility": self.visibility.value,
137
+ "payload": self.payload,
138
+ }
139
+
140
+ @classmethod
141
+ def from_dict(cls, d: dict[str, Any]) -> "PAEvent":
142
+ """Deserialize from dictionary."""
143
+ return cls(
144
+ run_id=d["run_id"],
145
+ session_id=d.get("session_id", ""),
146
+ lane_id=d.get("lane_id", "main"),
147
+ span_id=d.get("span_id", uuid.uuid4().hex[:12]),
148
+ parent_span_id=d.get("parent_span_id"),
149
+ seq=d.get("seq", 0),
150
+ ts=d.get("ts", time.time()),
151
+ kind=EventKind(d["kind"]),
152
+ visibility=Visibility(d.get("visibility", "public")),
153
+ payload=d.get("payload", {}),
154
+ )
155
+
156
+ @property
157
+ def is_terminal(self) -> bool:
158
+ """Is this a terminal event that must always be delivered?"""
159
+ return self.kind in TERMINAL_KINDS
160
+
161
+ @property
162
+ def is_error(self) -> bool:
163
+ return self.kind in (EventKind.RUN_ERROR, EventKind.AGENT_ERROR, EventKind.TOOL_ERROR)
164
+
165
+ def has_hidden_cot(self) -> bool:
166
+ """Check if payload contains raw chain-of-thought (safety violation)."""
167
+ forbidden_keys = {"hidden_chain_of_thought", "raw_cot", "internal_reasoning", "think_content"}
168
+ return bool(forbidden_keys & set(self.payload.keys()))
169
+
170
+
171
+ def create_event(
172
+ run_id: str,
173
+ kind: EventKind,
174
+ lane_id: str = "main",
175
+ seq: int = 0,
176
+ parent_span_id: str | None = None,
177
+ visibility: Visibility = Visibility.PUBLIC,
178
+ **payload,
179
+ ) -> PAEvent:
180
+ """Factory helper for creating events with less boilerplate."""
181
+ return PAEvent(
182
+ run_id=run_id,
183
+ lane_id=lane_id,
184
+ span_id=uuid.uuid4().hex[:12],
185
+ parent_span_id=parent_span_id,
186
+ seq=seq,
187
+ kind=kind,
188
+ visibility=visibility,
189
+ payload=payload,
190
+ )