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
//! The §7.3 **no-response wound**: a step whose driver died before the model
//! produced anything — **and, since bl-55d8, why**.
//!
//! Without this state such a step reads as a quiet one — `Framing::Killed`
//! paints the same ash "stopped" badge a mid-stream kill gets, over a
//! `0 attempts · 0 tok` row that looks like nothing happened, while STORIES S0
//! step 4 promises "any step failure is a rendered fact" and §7.3 that a failed
//! action is never stderr-only.
//!
//! The **state** is two observations — the same pair §3.5 already composes for
//! agent state, nothing stored (§5.1 #13):
//!
//! - **Unanswered on disk** — the step's `response.json` carries no bytes
//! (absent or zero-length) *and* its `meta.json` is absent. lernie writes
//! `meta.json` only after the model call returns (ARCH §2.3 — its dispatch
//! loop short-circuits on the call's own error before `write_meta`), so the
//! pair says exactly: the call emitted nothing and the step never settled.
//! - **Nobody driving** — a live driver's newest step is legitimately
//! unanswered for the moments between opening `response.json` and the first
//! streamed event, so the wound is claimed only of an agent whose lock is
//! free (§3.5). Never a false definite (§10).
//!
//! Only the newest step can be the one a driver is filling, so the liveness
//! observation gates that step alone — an earlier unanswered step is
//! unambiguous, and stays rendered as the place the conversation died.
//!
//! The **reason** is a third read of the same step directory, and it is the
//! whole of bl-55d8. lernie ARCH §2.3 on `stderr.log`, verbatim: *"the adapter
//! subprocess's stderr, appended once per attempt across the model call.
//! **Empty on an ordinary run**: brazen speaks every failure in-band on stdout
//! (§4.4), so bytes here mean the adapter failed outside that contract — a
//! startup failure (a malformed brazen config, an unreadable credstore) that
//! produced no events at all."* That is this wound's class exactly, so the
//! file is not a hint about the cause — it **is** the cause, in the adapter's
//! own words, sitting in the step yog is already reading.
//!
//! **It is not a new state, and it is not a new stored fact** (bl-55d8): the
//! predicate is unchanged, the read is gated on it (a healthy step pays
//! nothing), and the words are re-read from disk every derivation like every
//! other §5.1 fact.
//!
//! **Where it is NOT.** Until bl-55d8 the banner pointed at the ops surface —
//! *"the driver's own stderr is in the activity trail below"*. For the class
//! the operator actually hit that pointer is empty: a turn continued by
//! `lernie message` is driven by a child **lernie** launched, not by a yog
//! detached spawn, so no §8.1 per-spawn sink exists to fold into a `-2` ops row
//! at all. The step's own `stderr.log` is the only copy, which is why the
//! sentence now carries the bytes instead of naming a place to look.
//!
//! Deliberately *not* a reproduction hatch (§8.4 `yog exec`): see §14's
//! rejection — the driver's own words are now rendered where the wound is, so
//! there is even less reason to ask the operator to re-create it.
use Path;
use crateAgentState;
/// The sentence yog renders at the wound — the §7.3 rendered fact for this
/// class. Used verbatim beside the Steps row and composed into the §11
/// Altitude-1 banner, so both surfaces say one thing.
pub const NO_RESPONSE: &str = "driver produced no response";
/// What the banner adds when the step's own `stderr.log` is empty too — the
/// honest end of the trail, said outright rather than pointing somewhere that
/// has nothing either.
const MUTE: &str = "and its stderr.log is empty too — nothing on disk says why";
/// How the banner introduces the captured bytes. It names the **file**, not a
/// surface, for the §8.3 fallback-grammar reason: an operator who wants more
/// than the tail must be told where the whole of it lives.
const SPOKE: &str = "its stderr.log says:";
/// The §11 banner's leading mark. Never the only carrier — the sentence beside
/// it states the fact in words (§11 glyph doctrine).
const ALARM: &str = "⚠";
/// The adapter subprocess's captured stderr, per step (lernie ARCH §2.3).
const STDERR_FILE: &str = "stderr.log";
/// The §7.3 no-response wound **and its reason** — three readings of one
/// derivation, never a stored flag (§5.1 #13).
///
/// Three variants rather than an `Option<String>` because a wound with nothing
/// to say is a real, distinct answer (a SIGKILL mid-call leaves an empty
/// `stderr.log`), and `Some("")` would spell it as a wound whose words are
/// blank — one fact with two encodings, which is how they drift.
/// Read one step's wound from its own bytes. `response` and `meta_present` are
/// the reads [`summarize`](super::summarize) already made — `meta_present` is
/// the *existence* of `meta.json`, not its parse, since a malformed meta still
/// means the step settled.
///
/// The `stderr.log` read is **gated on the predicate**: an ordinary step pays
/// one comparison and no syscall, so attaching the reason costs a healthy
/// conversation nothing. Both bounds on how much is read are borrowed, not
/// invented — [`crate::opslog::detached::captured`] for how much of a capture
/// file yog ever reads, [`crate::opslog::rows::stderr_tail`] for how much of a
/// stderr a *surface* says.
pub
/// Is a driver at work on this agent (§3.5)? Then its newest step is allowed
/// to be unanswered — a model call in flight, not a wound.
pub
/// The agent's **latest** step's wound — the §11 Altitude-1 banner's input,
/// read off an already-built [`super::StepsView`] (the one owner of the
/// per-step reading) exactly as the Login banner reads its own. It takes the
/// view, not the disk: the shell builds that view once per snapshot (§7.2
/// `SnapMemo`, bl-e90a), and a predicate that re-read the whole steps tree per
/// frame was the chat pane's frame-time cost.