@beremaran/ralphie 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +843 -0
- package/LICENSE +21 -0
- package/README.md +53 -2
- package/dist/ralphie.js +33689 -0
- package/docs/README.md +71 -0
- package/docs/architecture.md +159 -0
- package/docs/cli-reference.md +126 -0
- package/docs/configuration.md +269 -0
- package/docs/development.md +249 -0
- package/docs/getting-started.md +134 -0
- package/docs/operations-and-recovery.md +328 -0
- package/docs/safety.md +189 -0
- package/docs/workflows.md +411 -0
- package/package.json +83 -3
- package/vendor/mattpocock-skills/LICENSE +21 -0
- package/vendor/mattpocock-skills/code-review/SKILL.md +87 -0
- package/vendor/mattpocock-skills/code-review/agents/openai.yaml +3 -0
- package/vendor/mattpocock-skills/codebase-design/DEEPENING.md +37 -0
- package/vendor/mattpocock-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/vendor/mattpocock-skills/codebase-design/SKILL.md +114 -0
- package/vendor/mattpocock-skills/codebase-design/agents/openai.yaml +3 -0
- package/vendor/mattpocock-skills/diagnosing-bugs/SKILL.md +138 -0
- package/vendor/mattpocock-skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/vendor/mattpocock-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/vendor/mattpocock-skills/implement/SKILL.md +15 -0
- package/vendor/mattpocock-skills/implement/agents/openai.yaml +5 -0
- package/vendor/mattpocock-skills/lock.json +37 -0
- package/vendor/mattpocock-skills/tdd/SKILL.md +38 -0
- package/vendor/mattpocock-skills/tdd/agents/openai.yaml +3 -0
- package/vendor/mattpocock-skills/tdd/mocking.md +59 -0
- package/vendor/mattpocock-skills/tdd/tests.md +77 -0
- package/vendor/mattpocock-skills/to-tickets/SKILL.md +105 -0
- package/vendor/mattpocock-skills/to-tickets/agents/openai.yaml +5 -0
- package/vendor/mattpocock-skills/triage/AGENT-BRIEF.md +207 -0
- package/vendor/mattpocock-skills/triage/OUT-OF-SCOPE.md +105 -0
- package/vendor/mattpocock-skills/triage/SKILL.md +112 -0
- package/vendor/mattpocock-skills/triage/agents/openai.yaml +5 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Operations and recovery
|
|
2
|
+
|
|
3
|
+
This page is for operators inspecting a run, integrating Ralphie's output,
|
|
4
|
+
or understanding what remains after interruption or failure. It is the
|
|
5
|
+
authoritative reference for progress output, artifacts, state, cancellation,
|
|
6
|
+
and cleanup. Start at the [documentation index](README.md) for other audience
|
|
7
|
+
paths.
|
|
8
|
+
|
|
9
|
+
## Progress output
|
|
10
|
+
|
|
11
|
+
While each session runs, Ralphie translates its harness's native event stream
|
|
12
|
+
into one harness-neutral set of session events: assistant text and thinking,
|
|
13
|
+
tool calls, tool results, errors, and token usage. Every output mode renders
|
|
14
|
+
only these events, so output looks the same whichever harness ran the session.
|
|
15
|
+
Tasks and issues are intentionally processed sequentially so the event stream
|
|
16
|
+
remains ordered. JSON output carries every session event for integrations; see
|
|
17
|
+
[Session events](#session-events).
|
|
18
|
+
|
|
19
|
+
Ralphie adapts its presentation to its environment. `--output default`
|
|
20
|
+
resolves to the full-screen TUI only when stdin and stderr are both TTYs and
|
|
21
|
+
`CI` is neither `"true"` nor `"1"`; otherwise it uses append-only `plain`
|
|
22
|
+
lines.
|
|
23
|
+
|
|
24
|
+
- Interactive terminals get an OpenTUI application (the same rendering core
|
|
25
|
+
OpenCode 1.0 uses): a borderless layout with a background header line
|
|
26
|
+
(repository, pause state), an issue sidebar, a scrollable
|
|
27
|
+
transcript that streams assistant text as it arrives, and a footer status
|
|
28
|
+
line (stage, activity, elapsed time). Transcript turns start with a colored
|
|
29
|
+
`● <harness> · <title>` role label and blank-line separation; assistant text
|
|
30
|
+
is indented and plain, thinking is dim, each tool call is one row
|
|
31
|
+
(`✓ $ <command> · 1.2s`, `✓ read <path>`, `✗ <tool> <path> · failed:
|
|
32
|
+
<detail>`), and a session error is a red `✗ <message>` row. The sidebar
|
|
33
|
+
lists every issue discovered in the run with its outcome (`○` queued, `▶`
|
|
34
|
+
active, `✓` completed, `✗` failed, `⚠` hand-off, `−` skipped) and
|
|
35
|
+
follows the active issue until you navigate away with `[`/`]` or
|
|
36
|
+
Ctrl+Left/Right; each issue keeps its own transcript, so processed issues
|
|
37
|
+
stay browsable while the run continues. The queue starts
|
|
38
|
+
paused so the discovered plan can be inspected before work begins; `p`
|
|
39
|
+
resumes or pauses it between issues, `s` stops the queue after the active
|
|
40
|
+
issue and drains the run normally, and `q` (like Ctrl-C) cancels immediately.
|
|
41
|
+
The footer and sidebar hints show the pending pause or stop. Tool output, long commands, and deep paths stay inside the
|
|
42
|
+
transcript panel; resize is handled by the renderer; Ctrl-C is forwarded as
|
|
43
|
+
SIGINT so cancellation still restores the checkout and saves state; disposal
|
|
44
|
+
destroys the renderer and restores the terminal.
|
|
45
|
+
- CI and redirected output are the deterministic noninteractive fallback:
|
|
46
|
+
append-only, byte-identical across identical runs, with neither ANSI cursor
|
|
47
|
+
controls (`ESC`) nor carriage-return bytes; `stripTerminalControls` is an
|
|
48
|
+
identity no-op on these streams. Each session is a block that opens with
|
|
49
|
+
`╭─ <harness> · <title>` and closes with `╰─ done`. Assistant and thinking
|
|
50
|
+
text are buffered per block and printed as complete `│ ` lines; each tool
|
|
51
|
+
call gets one line, each tool completion one summary line
|
|
52
|
+
(`│ ✓ <tool> done` or `│ ✗ <tool> failed: <output>`), and each session
|
|
53
|
+
error one `│ ✗ <message>` line. Usage is not printed.
|
|
54
|
+
- `--output json` writes progress records and `session_event` records one per
|
|
55
|
+
line to stdout with stderr empty: every non-empty line parses as one
|
|
56
|
+
complete JSON record, human headers/glyphs never appear, and values are
|
|
57
|
+
preserved as supplied.
|
|
58
|
+
|
|
59
|
+
JSON events use a stable operational vocabulary and include `runId`,
|
|
60
|
+
`timestamp`, `stage`, `status`, and `message`. Pre-flight and hand-off events identify
|
|
61
|
+
whether agent work was skipped. Human-readable hand-off decisions name
|
|
62
|
+
the issue number and title and show the current/total queue position. JSON
|
|
63
|
+
output retains the complete event payload, including the structured details
|
|
64
|
+
field; human-readable output never renders that field. A
|
|
65
|
+
`hand-off` event includes its reason, summary, evidence, questions,
|
|
66
|
+
diagnostic or artifact path, and queue position.
|
|
67
|
+
Depending on the event, it may also include the repository, review attempt,
|
|
68
|
+
session ID, commit SHA, created issue numbers, or diagnostic paths. Supplied
|
|
69
|
+
progress-event values are preserved as-is; session transcripts are never
|
|
70
|
+
redacted, and terminal control sequences are stripped at the
|
|
71
|
+
reporting boundary.
|
|
72
|
+
|
|
73
|
+
### Session events
|
|
74
|
+
|
|
75
|
+
Each `session_event` record wraps one session event with the session it
|
|
76
|
+
belongs to:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{"type":"session_event","sessionID":"claude-7d3e2a1b","directory":"/work/owner/repo","harness":"claude","title":"Implement #42","event":{"type":"tool_call","callId":"call-1","name":"Bash","input":{"command":"bun test"}}}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Field | Meaning |
|
|
83
|
+
| ----------- | --------------------------------------------------------- |
|
|
84
|
+
| `sessionID` | Ralphie's id for the session. |
|
|
85
|
+
| `directory` | Working directory of the session. |
|
|
86
|
+
| `harness` | Name of the harness that ran the session, such as `claude`. |
|
|
87
|
+
| `title` | Human-readable session label; omitted when there is none. |
|
|
88
|
+
| `event` | One of the event shapes below, discriminated by `type`. |
|
|
89
|
+
|
|
90
|
+
`event` is one of:
|
|
91
|
+
|
|
92
|
+
- `{"type":"session_started"}`: the session began producing work.
|
|
93
|
+
- `{"type":"assistant_text","kind":"text"|"thinking","text":…,"done":…}`: a
|
|
94
|
+
fragment of assistant output. Fragments of one `kind` concatenate into a
|
|
95
|
+
block until one arrives with `done: true`. `text` is only the fragment: a
|
|
96
|
+
closing fragment may be empty, and a whole block may arrive as a single
|
|
97
|
+
fragment with `done: true`.
|
|
98
|
+
- `{"type":"tool_call","callId":…,"name":…,"input":{…}}`: the assistant
|
|
99
|
+
started a tool. `callId` pairs the call with its result.
|
|
100
|
+
- `{"type":"tool_result","callId":…,"name":…,"output":…,"isError":…}`: the
|
|
101
|
+
tool finished. `output` is its text output, empty when it has none.
|
|
102
|
+
- `{"type":"error","message":…}`: the harness or model reported a failure.
|
|
103
|
+
- `{"type":"usage","inputTokens":…,"outputTokens":…}`, optionally with
|
|
104
|
+
`cacheReadTokens`, `cacheWriteTokens`, and `costUsd`: tokens, and cost where
|
|
105
|
+
the harness reports it, consumed since the session's previous `usage` event.
|
|
106
|
+
Sum the events for a session total.
|
|
107
|
+
- `{"type":"session_finished"}`: the session stopped producing work. Failures
|
|
108
|
+
are reported by `error` events, not here.
|
|
109
|
+
|
|
110
|
+
Tool names and `input` fields are the harness's own (for example Claude Code's
|
|
111
|
+
`Bash` tool takes `command`), so consumers that need tool-specific detail should key
|
|
112
|
+
on `harness` as well as `name`.
|
|
113
|
+
|
|
114
|
+
## State and artifacts
|
|
115
|
+
|
|
116
|
+
The workspace's `.ralphie` directory contains only repository checkouts and
|
|
117
|
+
Ralphie's run state, events, and recovery artifacts. Harness credentials and
|
|
118
|
+
settings belong to each harness CLI and are never written under this path.
|
|
119
|
+
|
|
120
|
+
Run artifacts live under:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
<workspace>/.ralphie/runs/<run-id>/
|
|
124
|
+
├── state.json
|
|
125
|
+
├── events.jsonl
|
|
126
|
+
└── issues/
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
New runs write the durable event log to
|
|
130
|
+
`<workspace>/.ralphie/runs/<run-id>/events.jsonl`. The run closes the log
|
|
131
|
+
immediately before removing the workspace, so post-cleanup progress still
|
|
132
|
+
renders but is not persisted.
|
|
133
|
+
|
|
134
|
+
A normal issue execution obtains a durable per-issue artifact store at:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
<workspace>/.ralphie/runs/<run-id>/issues/<issue-number>/artifacts.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The store prevents accidental overwrites and records readiness deferrals,
|
|
141
|
+
pre-flight decisions, checkpoints, review attempts, commit messages, created
|
|
142
|
+
commits, resolution proof, decomposition decisions, and created child-number
|
|
143
|
+
mappings. Stale or legacy un-fingerprinted decisions are removed on load
|
|
144
|
+
without disturbing the other artifacts for the issue.
|
|
145
|
+
|
|
146
|
+
A successful or interrupted run uses this more detailed layout (harness
|
|
147
|
+
configuration is not stored in this tree):
|
|
148
|
+
|
|
149
|
+
```text
|
|
150
|
+
<workspace>/.ralphie/runs/<run-id>/
|
|
151
|
+
├── state.json
|
|
152
|
+
├── events.jsonl
|
|
153
|
+
└── issues/
|
|
154
|
+
└── <issue-number>/
|
|
155
|
+
├── artifacts.json
|
|
156
|
+
├── review-exhaustion/
|
|
157
|
+
│ ├── changes.patch
|
|
158
|
+
│ └── metadata.json
|
|
159
|
+
└── hand-off-<id>/
|
|
160
|
+
├── changes.patch
|
|
161
|
+
└── metadata.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`state.json` is versioned, schema-validated, and atomically replaced. It
|
|
165
|
+
contains the repository/branch, the role assignments
|
|
166
|
+
(harness, model, effort),
|
|
167
|
+
pending and completed queue numbers, processed count, outcomes, active
|
|
168
|
+
issue/stage, checkout invariant, and update time. State is saved before the
|
|
169
|
+
queue starts, when an issue becomes active, after each issue outcome, after
|
|
170
|
+
queue refreshes, and at final completion. State is written for observability
|
|
171
|
+
only: Ralphie never loads a previous run's state.
|
|
172
|
+
|
|
173
|
+
## Failure, cancellation, and exit status
|
|
174
|
+
|
|
175
|
+
```mermaid
|
|
176
|
+
stateDiagram-v2
|
|
177
|
+
[*] --> Active: start
|
|
178
|
+
Active --> Active: persist issue/queue progress
|
|
179
|
+
Active --> Complete: queue empty
|
|
180
|
+
Active --> Stopped: error (saved as active)
|
|
181
|
+
Active --> Stopped: AbortSignal
|
|
182
|
+
Complete --> Cleaned: workspace removed after success
|
|
183
|
+
Stopped --> Retained: keep state/artifacts
|
|
184
|
+
Cleaned --> [*]
|
|
185
|
+
Retained --> [*]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- One issue failure restores its checkout, persists the failed outcome, retains
|
|
189
|
+
artifacts, and continues to later issues.
|
|
190
|
+
- Ordinary failures set process exit code `1`.
|
|
191
|
+
- A limit, outage or expired login ends the run early. The affected issue is
|
|
192
|
+
recorded as `deferred` after its checkout is restored, nothing on GitHub
|
|
193
|
+
changes, no later issue starts, and the process exits `75` with a message
|
|
194
|
+
that names the failure and the reset time when the harness reports one. State
|
|
195
|
+
and artifacts are kept and cleanup is skipped. Rerun after the limit clears
|
|
196
|
+
or you sign in again. Why this halts instead of handing off is recorded in
|
|
197
|
+
[ADR-0004](adr/0004-environmental-failures-halt-instead-of-handing-off.md).
|
|
198
|
+
- Cancellation is checked before long-running boundaries and passed to the running session, which is killed.
|
|
199
|
+
Ralphie attempts to restore the clean issue checkpoint, saves state with the
|
|
200
|
+
active issue, skips cleanup, and exits `130`.
|
|
201
|
+
- Successful completion persists `complete`, then removes the entire workspace
|
|
202
|
+
(after protected-path checks). Cleanup is skipped when the run drains with
|
|
203
|
+
issue failures, fails, or is cancelled, so state and diagnostics remain
|
|
204
|
+
available.
|
|
205
|
+
|
|
206
|
+
An ordinary issue failure never stops the queue. Ralphie restores the failed
|
|
207
|
+
issue checkout, records its outcome, and continues independent issues. Failed
|
|
208
|
+
prerequisites are not marked complete, so dependent issues remain blocked.
|
|
209
|
+
After draining all reachable work, the run exits with status `1` and an
|
|
210
|
+
aggregate partial-failure summary.
|
|
211
|
+
|
|
212
|
+
Hand-off outcomes also continue the queue. A drained run completes with
|
|
213
|
+
status `0`, and the handed-off issue remains open but leaves the intake queue
|
|
214
|
+
because it no longer carries the agent-ready label. The deterministic
|
|
215
|
+
`decomposition_limit_reached` boundary behaves the same way: raise the
|
|
216
|
+
persisted `limits.maxDecompositionDepth`, narrow the issue, or resolve its review
|
|
217
|
+
findings manually, then relabel the issue `ready-for-agent` for a later run.
|
|
218
|
+
It never closes or marks the capped issue complete, so dependent work remains
|
|
219
|
+
blocked.
|
|
220
|
+
|
|
221
|
+
## Hand-off handling
|
|
222
|
+
|
|
223
|
+
A validated hand-off decision is not an ordinary failure. Ralphie
|
|
224
|
+
persists the summary, evidence, questions, and issue
|
|
225
|
+
freshness metadata in the run artifacts, keeps the issue open, and continues
|
|
226
|
+
with later work.
|
|
227
|
+
|
|
228
|
+
The labels and comments a hand-off publishes are described under
|
|
229
|
+
[Hand-offs](workflows.md#hand-offs). They are published after recording the
|
|
230
|
+
outcome and before moving to the next issue. The comment carries a hidden
|
|
231
|
+
`ralphie:hand-off` marker, so a retry updates it instead of posting another.
|
|
232
|
+
Issues held back by open
|
|
233
|
+
blockers, whether reported by pre-flight or by queue order, are recorded as
|
|
234
|
+
skipped and change nothing on GitHub. A hand-off publishing failure fails the
|
|
235
|
+
run; the issue keeps its labels and a later run re-evaluates it from scratch.
|
|
236
|
+
|
|
237
|
+
When an executor session or pre-flight asks for a hand-off, Ralphie first persists the
|
|
238
|
+
bounded request, clean checkpoint, and issue freshness fingerprint. Exactly one
|
|
239
|
+
fresh read-only hand-off verification session verifies that request before the next artifact,
|
|
240
|
+
Git, or GitHub mutation. Only a `hand_off` verifier disposition confirms
|
|
241
|
+
it; actionable and already-resolved dispositions continue the original flow.
|
|
242
|
+
The confirmed decision is persisted before recovery writes a bounded binary-safe
|
|
243
|
+
patch and decision diagnostic, then restores and verifies the exact clean
|
|
244
|
+
checkpoint. A verifier or recovery interruption retains the pending hand-off so a later
|
|
245
|
+
attempt can retry verification or recovery without rerunning completed agent
|
|
246
|
+
work. The saved decision and pending hand-off are reused only when live `updatedAt` and
|
|
247
|
+
comment freshness metadata exactly match; a changed or invalid fingerprint
|
|
248
|
+
removes both atomically before routing continues.
|
|
249
|
+
|
|
250
|
+
```mermaid
|
|
251
|
+
stateDiagram-v2
|
|
252
|
+
state "Issue in progress" as IssueInProgress
|
|
253
|
+
state "Recoverable stop" as RecoverableStop
|
|
254
|
+
state "Artifacts retained" as Retained
|
|
255
|
+
state "Workspace cleaned" as Cleaned
|
|
256
|
+
|
|
257
|
+
[*] --> Active: Start
|
|
258
|
+
Active --> IssueInProgress: Dequeue issue
|
|
259
|
+
IssueInProgress --> Active: Persist outcome and queue
|
|
260
|
+
IssueInProgress --> RecoverableStop: Failure or interruption
|
|
261
|
+
Active --> Complete: Queue empty
|
|
262
|
+
Complete --> Cleaned: Remove workspace after success
|
|
263
|
+
RecoverableStop --> Retained: Keep workspace
|
|
264
|
+
Retained --> [*]
|
|
265
|
+
Cleaned --> [*]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Hand-off recovery diagnostics use the same issue directory and contain
|
|
269
|
+
`changes.patch` plus `metadata.json` under a fingerprint-bound
|
|
270
|
+
`hand-off-<id>/` directory. The patch includes tracked staged and unstaged
|
|
271
|
+
changes as well as untracked files. Matching diagnostics are reused within the
|
|
272
|
+
run; a fresh fingerprint receives a distinct directory. Diagnostics are
|
|
273
|
+
published atomically before the exact checkpoint is restored and verified.
|
|
274
|
+
|
|
275
|
+
## Interruption and recovery
|
|
276
|
+
|
|
277
|
+
There is no resume command. When a run fails or is interrupted:
|
|
278
|
+
|
|
279
|
+
1. the process exits non-zero (see
|
|
280
|
+
[exit status](#failure-cancellation-and-exit-status));
|
|
281
|
+
2. the workspace retains `state.json`, `events.jsonl`, and per-issue artifacts
|
|
282
|
+
for diagnosis;
|
|
283
|
+
3. issues that were not closed remain open and are selected again on the next
|
|
284
|
+
run; and
|
|
285
|
+
4. the next run removes the workspace, prepares a fresh checkout, and
|
|
286
|
+
re-evaluates every matching open issue from scratch.
|
|
287
|
+
|
|
288
|
+
A hard crash (a killed process, a power loss) in the middle of the review gate
|
|
289
|
+
is the one case that leaves the retained checkout unclean: Ralphie creates
|
|
290
|
+
local candidate commits while review is in progress, so HEAD may sit at a
|
|
291
|
+
candidate commit. Candidate commits are never pushed, and the next run
|
|
292
|
+
discards the whole workspace; inspect the retained checkout before starting
|
|
293
|
+
it if you need the work. Ordinary failures and cancellation restore the
|
|
294
|
+
checkpoint first.
|
|
295
|
+
|
|
296
|
+
Because each run starts clean, recovery is a new run rather than a continuation:
|
|
297
|
+
completed issues are already closed and no longer selected, while interrupted
|
|
298
|
+
issues repeat pre-flight, implementation, and review. Inspect the retained
|
|
299
|
+
`state.json` and artifacts before deleting them if the failure needs
|
|
300
|
+
investigation.
|
|
301
|
+
|
|
302
|
+
Native sub-issue and dependency endpoints require `github.com`; GitHub
|
|
303
|
+
Enterprise Server is not supported by the current client. With an unavailable
|
|
304
|
+
endpoint or a token lacking issue write permission, live decomposition fails with
|
|
305
|
+
an actionable error naming the missing capability; there is no body-link fallback.
|
|
306
|
+
See [Workflows](workflows.md#platform-support-for-native-sub-issues-and-dependencies).
|
|
307
|
+
|
|
308
|
+
An ordinary issue failure never stops the queue. Ralphie restores the failed
|
|
309
|
+
issue checkout, records its outcome, and continues independent work; the
|
|
310
|
+
drained run exits `1` if any issue failed.
|
|
311
|
+
|
|
312
|
+
A configured deterministic verification command returning non-zero is handled
|
|
313
|
+
before it becomes an issue failure. Ralphie gives the bounded command output and
|
|
314
|
+
staged diff to the implementer's session (resumed with `/diagnosing-bugs`, or a
|
|
315
|
+
fresh fixer session when it cannot be resumed), restages its changes, and
|
|
316
|
+
retries up to `limits.verificationFixes` times. Only repair exhaustion or a non-repairable
|
|
317
|
+
verification fault (for example a command changing the staged tree) reaches the
|
|
318
|
+
ordinary failure boundary. When no `verify` commands are configured, the gate
|
|
319
|
+
is skipped.
|
|
320
|
+
|
|
321
|
+
## Cleanup
|
|
322
|
+
|
|
323
|
+
Ralphie removes the entire workspace before preparing a run, after
|
|
324
|
+
protected-path checks, and again after a successful run. This deletes completed
|
|
325
|
+
state, events, diagnostics, and the repository checkout. Cleanup is skipped
|
|
326
|
+
when the run drains with issue failures, fails, or is cancelled, so state and
|
|
327
|
+
diagnostics remain available. Use a path dedicated to Ralphie; see
|
|
328
|
+
[Safety](safety.md) for the destructive workspace contract.
|
package/docs/safety.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Safety model
|
|
2
|
+
|
|
3
|
+
This page is for operators before they run Ralphie against a repository and for
|
|
4
|
+
contributors changing mutation paths. It is the authoritative reference for
|
|
5
|
+
Git/GitHub mutation boundaries, remote invariants, and workspace risks. Return
|
|
6
|
+
to the [documentation index](README.md) for the full
|
|
7
|
+
reading map.
|
|
8
|
+
|
|
9
|
+
> [!CAUTION]
|
|
10
|
+
> Ralphie commits approved work and pushes directly to the branch selected by
|
|
11
|
+
> `repos."owner/repo".branch` (default `main`, otherwise `master`). Test against a disposable repository before enabling mutations.
|
|
12
|
+
|
|
13
|
+
## Delivery guardrails
|
|
14
|
+
|
|
15
|
+
Delivery automation deserves explicit guardrails. Before agent work and again
|
|
16
|
+
before a push, Ralphie verifies that:
|
|
17
|
+
|
|
18
|
+
- the checkout and `origin` match the requested GitHub repository;
|
|
19
|
+
- the local checkout is still on the selected branch and expected commit;
|
|
20
|
+
- the remote branch has not moved from the captured base;
|
|
21
|
+
- the result is exactly the expected local commit; and
|
|
22
|
+
- the push is non-force.
|
|
23
|
+
|
|
24
|
+
If any invariant fails, Ralphie halts instead of guessing or retrying a
|
|
25
|
+
dangerous operation.
|
|
26
|
+
|
|
27
|
+
Delivery is one deterministic operation: it creates exactly one commit from the
|
|
28
|
+
allowed staged tree, re-checks the local branch/head and the remote base
|
|
29
|
+
immediately before the push, and pushes only with Git's non-force mode. A push
|
|
30
|
+
response is not proof; an authoritative remote branch read establishes whether
|
|
31
|
+
the commit arrived, including reconciliation of a lost push response. Movement
|
|
32
|
+
detected before staging/commit prevents the commit from being created; movement
|
|
33
|
+
detected before or during delivery is never followed, reset over, or
|
|
34
|
+
force-pushed over. Cancellation is checked at every mutation boundary, the push
|
|
35
|
+
is attempted at most once, and failures and cancellations leave a clean,
|
|
36
|
+
recoverable checkout.
|
|
37
|
+
|
|
38
|
+
Implementation agents may use whatever shell their harness grants. Ralphie does
|
|
39
|
+
not filter their commands: the guardrails are the session environment (see
|
|
40
|
+
[Session isolation](#session-isolation)) and the deterministic repository
|
|
41
|
+
invariants and delivery services, which remain authoritative.
|
|
42
|
+
|
|
43
|
+
## Workspace risk
|
|
44
|
+
|
|
45
|
+
There is one intentionally destructive local behavior: when reusing an existing
|
|
46
|
+
repository checkout that is not clean, Ralphie runs the equivalent of `git reset
|
|
47
|
+
--hard` and `git clean -fd`, then aligns it with the selected remote branch.
|
|
48
|
+
Tracked modifications and untracked, non-ignored files inside that checkout are
|
|
49
|
+
discarded. Keep unrelated work outside Ralphie's workspace.
|
|
50
|
+
|
|
51
|
+
The workspace's `.ralphie` directory contains only repository checkouts and
|
|
52
|
+
Ralphie's run state, events, and recovery artifacts. Harness credentials and
|
|
53
|
+
settings belong to each harness CLI and are never written under this path;
|
|
54
|
+
keep provider configuration outside the workspace.
|
|
55
|
+
|
|
56
|
+
Ralphie removes the entire workspace recursively before preparing a run and
|
|
57
|
+
again after a successful run, after protected-path checks. The retained
|
|
58
|
+
workspace after a failure is
|
|
59
|
+
[documented with cleanup and recovery](operations-and-recovery.md#cleanup). Use
|
|
60
|
+
a path dedicated to Ralphie:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bunx @beremaran/ralphie owner/repository \
|
|
64
|
+
--set workspace=/tmp/ralphie
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Agent and mutation boundaries
|
|
68
|
+
|
|
69
|
+
Agent sessions run as headless harness CLI invocations rooted at the
|
|
70
|
+
repository checkout. Read-only roles run in the harness's read-only mode
|
|
71
|
+
(Claude Code plan mode limited to the Read, Glob and Grep tools, so there is
|
|
72
|
+
no shell: reviewers cannot run `git diff`, and Ralphie puts the diff in their
|
|
73
|
+
prompt instead); the `implementer` and `fixer` run under their
|
|
74
|
+
[approval mode](#approval-modes) and may edit the checkout. Post-task
|
|
75
|
+
verification fails the task when the checkout's branch or head moved anyway.
|
|
76
|
+
Structured decisions are returned as a result validated against the canonical
|
|
77
|
+
Zod schema (natively where the harness supports it, otherwise from a final
|
|
78
|
+
JSON block, with a bounded number of corrections), and the validated value is
|
|
79
|
+
what the domain boundary accepts. A repository-backed blocker is the
|
|
80
|
+
implementer's `needs_attention` status with a `needsAttention` object (`reason`
|
|
81
|
+
and `questions`) in that result, or a pre-flight `hand_off` disposition; neither
|
|
82
|
+
is a mutation-capable tool, and Ralphie verifies the request before handing
|
|
83
|
+
off. Ralphie
|
|
84
|
+
stages, verifies, commits, pushes, and mutates
|
|
85
|
+
GitHub through deterministic domain services. Invalid output or a harness failure
|
|
86
|
+
becomes a failed issue outcome without proceeding to the next operation.
|
|
87
|
+
A turn that produces no assistant message fails instead of producing a
|
|
88
|
+
decision.
|
|
89
|
+
|
|
90
|
+
Protected maintainer choices are also enforced before verification: a staged
|
|
91
|
+
change that selects a project license fails closed unless that exact license
|
|
92
|
+
is authorized by the issue text, deferring to a maintainer decision instead of
|
|
93
|
+
silently establishing policy.
|
|
94
|
+
|
|
95
|
+
Verification is opt-in: Ralphie runs only the commands listed under
|
|
96
|
+
`repos."owner/repo".verify`, and when none are listed the gate is skipped and review
|
|
97
|
+
proceeds on the staged diff. Configured commands run against the staged tree
|
|
98
|
+
and their evidence is
|
|
99
|
+
bound to that tree before review or commit. A non-zero command exit is treated
|
|
100
|
+
as actionable implementation feedback: the fix session (the implementer's, resumed) receives bounded
|
|
101
|
+
failure evidence, and Ralphie restages and retries up to `limits.verificationFixes` times.
|
|
102
|
+
Staged-tree mutation and exhausted repair remain
|
|
103
|
+
hard safety stops. The direct-push path never uses force. See
|
|
104
|
+
[Workflows](workflows.md) for the complete implementation and delivery sequence,
|
|
105
|
+
and [Operations and recovery](operations-and-recovery.md) for what remains
|
|
106
|
+
available after a safety stop.
|
|
107
|
+
|
|
108
|
+
## Approval modes
|
|
109
|
+
|
|
110
|
+
Read-only roles never edit and ignore the approval mode. The editing roles
|
|
111
|
+
(`implementer` and `fixer`) run under `approval`, set at the top level of the
|
|
112
|
+
configuration and overridable per repository and per harness
|
|
113
|
+
(`harnesses.<name>.approval`):
|
|
114
|
+
|
|
115
|
+
- `safe` (default) uses the harness's own approval or sandbox: Claude Code
|
|
116
|
+
auto mode, or the Codex workspace-write sandbox.
|
|
117
|
+
- `yolo` turns off every approval and sandbox check (Claude Code
|
|
118
|
+
`bypassPermissions`, Codex `--dangerously-bypass-approvals-and-sandbox`, and
|
|
119
|
+
the only mode pi and OpenCode have). Use it only in an environment that is
|
|
120
|
+
already isolated.
|
|
121
|
+
|
|
122
|
+
Before any work starts, Ralphie checks that every assigned harness starts,
|
|
123
|
+
that `safe` is actually granted where configured (Claude Code can silently
|
|
124
|
+
fall back from auto mode), and that no editing role runs on pi or OpenCode
|
|
125
|
+
without `yolo`, because neither has a sandbox or approval system. A failure
|
|
126
|
+
stops the run within seconds and names the configuration change that fixes it,
|
|
127
|
+
such as `harnesses.pi.approval: yolo` or moving the role with
|
|
128
|
+
`roles.implementer`. Spend caps (`limits.maxBudgetUsd`) are covered in
|
|
129
|
+
[Configuration](configuration.md#limits).
|
|
130
|
+
|
|
131
|
+
Each session also has a wall-clock limit
|
|
132
|
+
([`limits.sessionTimeoutMinutes`](configuration.md#limits)); exceeding it kills
|
|
133
|
+
the session's whole process group, so tools the harness started die with it.
|
|
134
|
+
|
|
135
|
+
## Session isolation
|
|
136
|
+
|
|
137
|
+
Sessions never hold GitHub or push authority (ADR-0003); the session
|
|
138
|
+
environment enforces it instead of the prompts.
|
|
139
|
+
|
|
140
|
+
- **No credentials.** Every session starts without `GH_TOKEN`, `GITHUB_TOKEN`,
|
|
141
|
+
`GH_ENTERPRISE_TOKEN` and `GITHUB_ENTERPRISE_TOKEN`, and with `GH_CONFIG_DIR`
|
|
142
|
+
pointing at a fresh, empty temporary directory that is removed when the
|
|
143
|
+
session ends, so a stored `gh` login is not visible either. These entries
|
|
144
|
+
override anything a request sets.
|
|
145
|
+
- **No SSH or git credentials.** Every session also starts without
|
|
146
|
+
`SSH_AUTH_SOCK`, `SSH_ASKPASS` and `GIT_ASKPASS`, with `GIT_CONFIG_GLOBAL`
|
|
147
|
+
and `GIT_CONFIG_SYSTEM` set to `/dev/null` and the repository's
|
|
148
|
+
`credential.helper` list reset (so no credential helper applies),
|
|
149
|
+
`GIT_TERMINAL_PROMPT=0`, and `GIT_SSH_COMMAND=false`, so a git remote cannot
|
|
150
|
+
authenticate through an ssh agent, a helper or a prompt. Ralphie's own
|
|
151
|
+
delivery push runs outside sessions and is unaffected.
|
|
152
|
+
- **Residual limitation.** The environment cannot hide credentials that a
|
|
153
|
+
process running as the same OS user can read directly: a private key under
|
|
154
|
+
`~/.ssh` used through an explicit `ssh -i`, a `gh` token read straight out of
|
|
155
|
+
the operating system keyring (for example with `security find-generic-password`
|
|
156
|
+
on macOS), or a credential file read by path. `gh` itself is logged out in a
|
|
157
|
+
session even when its token lives in the keyring, because it finds keyring
|
|
158
|
+
entries through the hosts file in its now-empty config directory. Ralphie does not claim to block these, and a determined
|
|
159
|
+
yolo session could still use them. What it guarantees is that it never hands
|
|
160
|
+
a token to a session, that the checkout's push URL is disabled, and that its
|
|
161
|
+
own delivery push is verified against the remote. To close the gap, run
|
|
162
|
+
Ralphie as a dedicated OS user whose home, keyring and ssh keys hold no
|
|
163
|
+
credentials for the repository.
|
|
164
|
+
- **No push from the workspace.** After preparing the checkout Ralphie sets
|
|
165
|
+
origin's push URL to a disabled value, so `git push` inside the workspace
|
|
166
|
+
fails. Ralphie's own delivery push names the fetch URL explicitly, never
|
|
167
|
+
uses force, and is verified against the remote afterwards.
|
|
168
|
+
- **Read-only means unchanged.** Before and after every read-only session
|
|
169
|
+
Ralphie fingerprints HEAD, the index, tracked changes and untracked file
|
|
170
|
+
contents. Any difference fails the session (kind `access`), which fails the
|
|
171
|
+
issue closed.
|
|
172
|
+
|
|
173
|
+
## Bounded command execution
|
|
174
|
+
|
|
175
|
+
No command runs unbounded. Every process Ralphie spawns carries a hard
|
|
176
|
+
deadline so a hung process fails loudly instead of stalling an issue run:
|
|
177
|
+
|
|
178
|
+
- **Sessions** are bounded by `limits.sessionTimeoutMinutes`, described above.
|
|
179
|
+
- **Ralphie-owned commands** (git and `gh` operations against the repository,
|
|
180
|
+
workspace preparation, authentication checks) default to a 10-minute timeout.
|
|
181
|
+
- **Verification commands** (`verify`) run under a 30-minute timeout
|
|
182
|
+
because they execute the repository's full gate; they are the deliberate
|
|
183
|
+
exception to the shorter defaults. When no command is configured, no
|
|
184
|
+
verification process runs.
|
|
185
|
+
|
|
186
|
+
A timed-out command is killed (including its process tree) and reported as
|
|
187
|
+
`CommandTimeoutError` with the deadline and command in the message. These
|
|
188
|
+
deadlines are fail-closed bounds, not retry budgets: they turn an indefinitely
|
|
189
|
+
stuck session into a recoverable, reported failure.
|