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
//! Role + Class: the role.class.sub label taxonomy (Class::ALL is the source of truth).
/// The top-level ROLE of a classified record (GOLD §2). The first dot-segment of every
/// [`Class::path`]. A multi-label record can span roles (e.g. an AUQ answer is both
/// [`Role::User`] and [`Role::Agent`]).
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Role {
/// The human operator.
User,
/// The assistant (incl. its tool I/O and peer communication).
Agent,
/// Claude Code machinery (notifications, compaction, slash wrappers, interrupts, schedule).
Harness,
}
#[allow(dead_code)]
impl Role {
/// The stable lowercase slug (the first dot-segment of a [`Class::path`]).
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Role::User => "user",
Role::Agent => "agent",
Role::Harness => "harness",
}
}
}
/// A LEAF class in the role.class.sub taxonomy (GOLD §2). One variant per leaf; the dotted
/// [`Class::path`] is the canonical wire/selector form and [`Class::role`] its top-level role.
/// A record carries a `Vec<Class>` (multi-label, GOLD §3) via [`Record::classify`].
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Class {
/// `user.message` - genuine human prose (incl. slash-command `<command-args>`).
UserMessage,
/// `user.answer` - an AskUserQuestion answer (the Q+options+answer unit). Dual-labeled
/// with [`Class::AgentToolResult`] (it rides on the answering tool_result carrier).
UserAnswer,
/// `user.rejection` - a plan/tool rejection carrying a typed instruction. Dual-labeled
/// with [`Class::AgentToolResult`].
UserRejection,
/// `agent.message` - the assistant's visible end-of-turn text block(s).
AgentMessage,
/// `agent.thinking` - a thinking block (see the GOLD-gap note re `redacted_thinking`).
AgentThinking,
/// `agent.tool.use` - a tool_use block (incl. a pending elicitation sidecar marker).
AgentToolUse,
/// `agent.tool.result` - a tool_result block (incl. errored).
AgentToolResult,
/// `agent.communication.inbox` - a received peer message / spawn prompt / subagent return.
CommInbox,
/// `agent.communication.sent` - a sent peer message (`SendMessage`) or a spawn.
CommSent,
/// `agent.communication.signal` - a control/status comm (idle_notification, shutdown_*).
CommSignal,
/// `harness.notification.workflow` - a `<task-notification>` for a dynamic/OMC workflow.
NotificationWorkflow,
/// `harness.notification.monitor` - a monitor/cron cadence completion pulse.
NotificationMonitor,
/// `harness.notification.subagent` - a spawned-subagent completion pulse (renamed from
/// [`AutomationKind::Agent`] so it never collides with the `agent` role).
NotificationSubagent,
/// `harness.notification.background-command` - a `&`-detached shell command pulse.
NotificationBackgroundCommand,
/// `harness.notification.task` - any other / unclassified `<task-notification>`.
NotificationTask,
/// `harness.compaction.summary` - the `isCompactSummary` summary record.
CompactionSummary,
/// `harness.compaction.boundary` - the `system`/`compact_boundary` metrics record.
CompactionBoundary,
/// `harness.command.invocation` - a `<command-name>…` slash-command wrapper.
CommandInvocation,
/// `harness.command.stdout` - a `<local-command-stdout>…` local-command output.
CommandStdout,
/// `harness.interrupt.user` - `[Request interrupted by user]`.
InterruptUser,
/// `harness.interrupt.tool` - `[Request interrupted by user for tool use]`.
InterruptTool,
/// `harness.schedule.wakeup` - a fired `ScheduleWakeup` TIMER tick (its injected
/// [`SCHEDULE_WAKEUP_MARKER`] prompt). Distinct from [`Class::MetaLoop`] (the
/// autonomous-loop driver prose); the timer is the harness scheduler firing.
ScheduleWakeup,
/// `harness.schedule.continuation` - a `Continue from where you left off.` resume tick.
ScheduleContinuation,
/// `harness.meta.hook` - hook-injected feedback (stop-hook / `<local-command-caveat>` /
/// edit-failed-retry), not the operator.
MetaHook,
/// `harness.meta.loop` - an autonomous-loop driver tick (`# Autonomous loop tick` /
/// `Run the autonomous check`).
MetaLoop,
}
#[allow(dead_code)]
impl Class {
/// Every leaf [`Class`] in taxonomy order (GOLD §2). The single source of truth for
/// enumerating the class space - P2 builds the `-t` selector table from it, and tests
/// assert `path()`/`role()` exhaustively over it (a new variant added to the enum but not
/// here is caught by the `all_classes_cover_the_enum` test). Order: user, agent (+comm),
/// harness (notification, compaction, command, interrupt, schedule, meta).
pub const ALL: &'static [Class] = &[
Class::UserMessage,
Class::UserAnswer,
Class::UserRejection,
Class::AgentMessage,
Class::AgentThinking,
Class::AgentToolUse,
Class::AgentToolResult,
Class::CommInbox,
Class::CommSent,
Class::CommSignal,
Class::NotificationWorkflow,
Class::NotificationMonitor,
Class::NotificationSubagent,
Class::NotificationBackgroundCommand,
Class::NotificationTask,
Class::CompactionSummary,
Class::CompactionBoundary,
Class::CommandInvocation,
Class::CommandStdout,
Class::InterruptUser,
Class::InterruptTool,
Class::ScheduleWakeup,
Class::ScheduleContinuation,
Class::MetaHook,
Class::MetaLoop,
];
/// The canonical dotted path (GOLD §2) - the `-t` selector form (P2) and render label.
#[must_use]
pub fn path(self) -> &'static str {
match self {
Class::UserMessage => "user.message",
Class::UserAnswer => "user.answer",
Class::UserRejection => "user.rejection",
Class::AgentMessage => "agent.message",
Class::AgentThinking => "agent.thinking",
Class::AgentToolUse => "agent.tool.use",
Class::AgentToolResult => "agent.tool.result",
Class::CommInbox => "agent.communication.inbox",
Class::CommSent => "agent.communication.sent",
Class::CommSignal => "agent.communication.signal",
Class::NotificationWorkflow => "harness.notification.workflow",
Class::NotificationMonitor => "harness.notification.monitor",
Class::NotificationSubagent => "harness.notification.subagent",
Class::NotificationBackgroundCommand => "harness.notification.background-command",
Class::NotificationTask => "harness.notification.task",
Class::CompactionSummary => "harness.compaction.summary",
Class::CompactionBoundary => "harness.compaction.boundary",
Class::CommandInvocation => "harness.command.invocation",
Class::CommandStdout => "harness.command.stdout",
Class::InterruptUser => "harness.interrupt.user",
Class::InterruptTool => "harness.interrupt.tool",
Class::ScheduleWakeup => "harness.schedule.wakeup",
Class::ScheduleContinuation => "harness.schedule.continuation",
Class::MetaHook => "harness.meta.hook",
Class::MetaLoop => "harness.meta.loop",
}
}
/// The top-level role (the first dot-segment of [`Class::path`]). Exhaustive (no
/// wildcard) so a future leaf forces an explicit role decision at compile time.
#[must_use]
pub fn role(self) -> Role {
match self {
Class::UserMessage | Class::UserAnswer | Class::UserRejection => Role::User,
Class::AgentMessage
| Class::AgentThinking
| Class::AgentToolUse
| Class::AgentToolResult
| Class::CommInbox
| Class::CommSent
| Class::CommSignal => Role::Agent,
Class::NotificationWorkflow
| Class::NotificationMonitor
| Class::NotificationSubagent
| Class::NotificationBackgroundCommand
| Class::NotificationTask
| Class::CompactionSummary
| Class::CompactionBoundary
| Class::CommandInvocation
| Class::CommandStdout
| Class::InterruptUser
| Class::InterruptTool
| Class::ScheduleWakeup
| Class::ScheduleContinuation
| Class::MetaHook
| Class::MetaLoop => Role::Harness,
}
}
}