ralph.rb 1.2.4355354345 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/gem-push.yml +2 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +53 -0
- data/lib/ralph/cli.rb +67 -186
- data/lib/ralph/display.rb +105 -0
- data/lib/ralph/events.rb +117 -0
- data/lib/ralph/loop.rb +113 -170
- data/lib/ralph/metrics.rb +88 -0
- data/lib/ralph/opencode.rb +66 -0
- data/lib/ralph/version.rb +1 -1
- data/lib/ralph.rb +0 -3
- data/plans/00-complete-implementation.md +120 -0
- data/plans/01-cli-implementation.md +53 -0
- data/plans/02-loop-implementation.md +78 -0
- data/plans/03-agents-implementation.md +76 -0
- data/plans/04-metrics-implementation.md +98 -0
- data/plans/README.md +63 -0
- data/specs/README.md +4 -15
- data/specs/__templates__/API_TEMPLATE.md +0 -0
- data/specs/__templates__/AUTOMATION_ACTION_TEMPLATE.md +0 -0
- data/specs/__templates__/AUTOMATION_TRIGGER_TEMPLATE.md +0 -0
- data/specs/__templates__/CONTROLLER_TEMPLATE.md +32 -0
- data/specs/__templates__/INTEGRATION_TEMPLATE.md +0 -0
- data/specs/__templates__/MODEL_TEMPLATE.md +0 -0
- data/specs/agents.md +426 -120
- data/specs/cli.md +11 -218
- data/specs/lib/todo_item.rb +144 -0
- data/specs/log +15 -0
- data/specs/loop.md +42 -0
- data/specs/metrics.md +51 -0
- metadata +23 -39
- data/lib/ralph/agents/base.rb +0 -132
- data/lib/ralph/agents/claude_code.rb +0 -24
- data/lib/ralph/agents/codex.rb +0 -25
- data/lib/ralph/agents/open_code.rb +0 -30
- data/lib/ralph/agents.rb +0 -24
- data/lib/ralph/config.rb +0 -40
- data/lib/ralph/git/file_snapshot.rb +0 -60
- data/lib/ralph/helpers.rb +0 -76
- data/lib/ralph/iteration.rb +0 -220
- data/lib/ralph/output/active_loop_error.rb +0 -13
- data/lib/ralph/output/banner.rb +0 -29
- data/lib/ralph/output/completion_deferred.rb +0 -12
- data/lib/ralph/output/completion_detected.rb +0 -17
- data/lib/ralph/output/config_summary.rb +0 -31
- data/lib/ralph/output/context_consumed.rb +0 -11
- data/lib/ralph/output/iteration.rb +0 -45
- data/lib/ralph/output/max_iterations_reached.rb +0 -16
- data/lib/ralph/output/no_plugin_warning.rb +0 -14
- data/lib/ralph/output/nonzero_exit_warning.rb +0 -11
- data/lib/ralph/output/plugin_error.rb +0 -12
- data/lib/ralph/output/status.rb +0 -176
- data/lib/ralph/output/struggle_warning.rb +0 -18
- data/lib/ralph/output/task_completion.rb +0 -12
- data/lib/ralph/output/tasks_file_created.rb +0 -11
- data/lib/ralph/prompt_template.rb +0 -183
- data/lib/ralph/storage/context.rb +0 -58
- data/lib/ralph/storage/history.rb +0 -117
- data/lib/ralph/storage/state.rb +0 -178
- data/lib/ralph/storage/tasks.rb +0 -244
- data/lib/ralph/threads/heartbeat.rb +0 -44
- data/lib/ralph/threads/stream_reader.rb +0 -50
- data/original/bin/ralph.js +0 -13
- data/original/ralph.ts +0 -1706
- data/specs/iteration.md +0 -173
- data/specs/output.md +0 -104
- data/specs/storage/local-data-structure.md +0 -246
- data/specs/tasks.md +0 -295
data/specs/cli.md
CHANGED
|
@@ -1,223 +1,16 @@
|
|
|
1
|
-
# CLI
|
|
1
|
+
# CLI specification
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
How will we interact with the program?
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
ralph "<prompt>" [options]
|
|
7
|
-
ralph <subcommand> [args]
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
## Prompt Input
|
|
11
|
-
|
|
12
|
-
The primary argument is a prompt string describing the task for the AI agent.
|
|
13
|
-
|
|
14
|
-
| Input method | Syntax | Notes |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| Inline string | `ralph "Build a REST API"` | One or more positional args joined with spaces |
|
|
17
|
-
| Piped input | `cat prompt.md \| ralph` | Read prompt from stdin via shell pipe |
|
|
18
|
-
| Command substitution | `ralph $(cat prompt.md)` | Expand file contents as arguments |
|
|
19
|
-
|
|
20
|
-
### Prompt resolution order
|
|
21
|
-
|
|
22
|
-
1. Join all positional args with spaces.
|
|
23
|
-
2. If the resolved prompt is empty, exit with an error.
|
|
24
|
-
|
|
25
|
-
## Loop Options
|
|
26
|
-
|
|
27
|
-
These flags configure the main iteration loop.
|
|
28
|
-
|
|
29
|
-
### `--agent AGENT`
|
|
30
|
-
|
|
31
|
-
Select the AI agent to use.
|
|
32
|
-
|
|
33
|
-
- **Type:** String, restricted to valid agent names
|
|
34
|
-
- **Valid values:** `opencode`, `claude-code`, `codex`
|
|
35
|
-
- **Default:** `opencode`
|
|
36
|
-
- **Error on invalid value:** Yes (OptionParser rejects with `invalid argument`)
|
|
37
|
-
|
|
38
|
-
### `--min-iterations N`
|
|
39
|
-
|
|
40
|
-
Minimum number of iterations before a completion promise is accepted.
|
|
41
|
-
|
|
42
|
-
- **Type:** Integer
|
|
43
|
-
- **Default:** `1`
|
|
44
|
-
- **Behavior:** If the agent outputs the completion promise before this many iterations have run, the loop continues anyway.
|
|
45
|
-
|
|
46
|
-
### `--max-iterations N`
|
|
47
|
-
|
|
48
|
-
Maximum number of iterations before the loop stops automatically.
|
|
49
|
-
|
|
50
|
-
- **Type:** Integer
|
|
51
|
-
- **Default:** `0` (unlimited)
|
|
52
|
-
- **Behavior:** When the iteration counter exceeds this value, the loop stops and clears state.
|
|
53
|
-
|
|
54
|
-
### Validation
|
|
55
|
-
|
|
56
|
-
`--min-iterations` must not exceed `--max-iterations` (when max > 0). The CLI exits with an error if this constraint is violated.
|
|
57
|
-
|
|
58
|
-
### `--completion-promise TEXT`
|
|
59
|
-
|
|
60
|
-
The phrase the agent must output (wrapped in `<promise>...</promise>` tags) to signal task completion.
|
|
61
|
-
|
|
62
|
-
- **Type:** String
|
|
63
|
-
- **Default:** `COMPLETE`
|
|
64
|
-
|
|
65
|
-
### `--tasks`, `-t`
|
|
66
|
-
|
|
67
|
-
Enable Tasks Mode for structured task tracking.
|
|
68
|
-
|
|
69
|
-
- **Type:** Boolean flag
|
|
70
|
-
- **Default:** `false`
|
|
71
|
-
- **Behavior:** When enabled, the loop works through tasks defined in `.ralph/ralph-tasks.md` one at a time. The prompt builder includes task-specific instructions and workflow guidance.
|
|
72
|
-
|
|
73
|
-
### `--task-promise TEXT`
|
|
74
|
-
|
|
75
|
-
The phrase the agent outputs to signal an individual task is complete (tasks mode only).
|
|
76
|
-
|
|
77
|
-
- **Type:** String
|
|
78
|
-
- **Default:** `READY_FOR_NEXT_TASK`
|
|
79
|
-
|
|
80
|
-
### `--model MODEL`
|
|
81
|
-
|
|
82
|
-
Model identifier passed to the selected agent.
|
|
83
|
-
|
|
84
|
-
- **Type:** String
|
|
85
|
-
- **Default:** `""` (agent's default)
|
|
86
|
-
- **Agent-specific:** The value is passed directly to the agent CLI (e.g., `--model` for claude-code/codex, `-m` for opencode).
|
|
87
|
-
|
|
88
|
-
### `--[no-]stream`
|
|
89
|
-
|
|
90
|
-
Control whether agent output is streamed in real-time or buffered.
|
|
91
|
-
|
|
92
|
-
- **Type:** Boolean
|
|
93
|
-
- **Default:** `true` (streaming on)
|
|
94
|
-
- **`--stream`:** Stream stdout/stderr in real-time with tool counting and heartbeat.
|
|
95
|
-
- **`--no-stream`:** Buffer all output, print at end. Tool counts collected post-hoc.
|
|
96
|
-
|
|
97
|
-
### `--verbose-tools`
|
|
98
|
-
|
|
99
|
-
Print every tool invocation line instead of periodic compact summaries.
|
|
100
|
-
|
|
101
|
-
- **Type:** Boolean flag
|
|
102
|
-
- **Default:** `false`
|
|
103
|
-
- **Behavior:** When off (default), tool lines are suppressed and a compact summary is printed every 3 seconds. When on, every tool line is printed as-is.
|
|
104
|
-
|
|
105
|
-
### `--no-plugins`
|
|
106
|
-
|
|
107
|
-
Disable non-auth OpenCode plugins for the current run.
|
|
108
|
-
|
|
109
|
-
- **Type:** Boolean flag
|
|
110
|
-
- **Default:** `false`
|
|
111
|
-
- **Agent-specific:** Only affects `opencode`. Prints a warning if used with `claude-code` or `codex`.
|
|
112
|
-
- **Behavior:** Generates a filtered OpenCode config that only includes plugins matching `/auth/i`.
|
|
113
|
-
|
|
114
|
-
### `--[no-]commit`
|
|
115
|
-
|
|
116
|
-
Control automatic git commits after each iteration.
|
|
117
|
-
|
|
118
|
-
- **Type:** Boolean
|
|
119
|
-
- **Default:** `true` (auto-commit on)
|
|
120
|
-
- **`--commit`:** After each iteration, if `git status --porcelain` shows changes, run `git add -A && git commit -m "Ralph iteration N: work in progress"`.
|
|
121
|
-
- **`--no-commit`:** Skip auto-commits.
|
|
122
|
-
|
|
123
|
-
### `--[no-]allow-all`
|
|
124
|
-
|
|
125
|
-
Control automatic tool permission approval.
|
|
126
|
-
|
|
127
|
-
- **Type:** Boolean
|
|
128
|
-
- **Default:** `true` (auto-approve on)
|
|
129
|
-
- **`--allow-all`:** Pass permission flags to the agent (`--dangerously-skip-permissions` for claude-code, `--full-auto` for codex, permission config for opencode).
|
|
130
|
-
- **`--no-allow-all`:** Require interactive permission prompts from the agent.
|
|
131
|
-
|
|
132
|
-
## Subcommands
|
|
133
|
-
|
|
134
|
-
Subcommands are flag-style (prefixed with `--`) and cause the CLI to perform a specific action then exit. They do not start the main loop.
|
|
135
|
-
|
|
136
|
-
### `--version`, `-v`
|
|
137
|
-
|
|
138
|
-
Print `ralph <VERSION>` and exit 0.
|
|
139
|
-
|
|
140
|
-
### `--help`, `-h`
|
|
141
|
-
|
|
142
|
-
Print the full help text (generated by OptionParser) and exit 0.
|
|
143
|
-
|
|
144
|
-
### `--status`
|
|
145
|
-
|
|
146
|
-
Display the current loop status, pending context, and iteration history, then exit 0.
|
|
147
|
-
|
|
148
|
-
- **With `-t` or `--tasks`:** Also display the current task list from `.ralph/ralph-tasks.md`.
|
|
149
|
-
- **When an active loop has `tasks_mode` enabled:** Task list is shown automatically.
|
|
150
|
-
- **Output includes:**
|
|
151
|
-
- Active/inactive status
|
|
152
|
-
- Current iteration, start time, elapsed time
|
|
153
|
-
- Completion promise, agent, model
|
|
154
|
-
- Pending context (if any)
|
|
155
|
-
- Task list with progress (if shown)
|
|
156
|
-
- Last 5 iterations with duration and top tools
|
|
157
|
-
- Struggle indicators (if detected)
|
|
158
|
-
|
|
159
|
-
### `--add-context TEXT`
|
|
160
|
-
|
|
161
|
-
Append context text to `.ralph/ralph-context.md` for the next iteration.
|
|
162
|
-
|
|
163
|
-
- **Requires:** A text argument (exits with error if missing)
|
|
164
|
-
- **Behavior:** Appends a timestamped section. Creates the file if it doesn't exist. The context is injected into the prompt on the next iteration, then cleared.
|
|
165
|
-
|
|
166
|
-
### `--clear-context`
|
|
167
|
-
|
|
168
|
-
Delete `.ralph/ralph-context.md` if it exists.
|
|
169
|
-
|
|
170
|
-
### `--list-tasks`
|
|
171
|
-
|
|
172
|
-
Parse and display tasks from `.ralph/ralph-tasks.md` with numbered indices and status icons.
|
|
173
|
-
|
|
174
|
-
- Exits with a message if no tasks file exists.
|
|
175
|
-
|
|
176
|
-
### `--add-task DESC`
|
|
177
|
-
|
|
178
|
-
Append a new `- [ ] DESC` line to `.ralph/ralph-tasks.md`.
|
|
179
|
-
|
|
180
|
-
- **Requires:** A description argument.
|
|
181
|
-
- Creates the file with a header if it doesn't exist.
|
|
182
|
-
|
|
183
|
-
### `--remove-task N`
|
|
184
|
-
|
|
185
|
-
Remove task at 1-based index N (including its subtasks) from `.ralph/ralph-tasks.md`.
|
|
186
|
-
|
|
187
|
-
- **Requires:** An integer argument.
|
|
188
|
-
- **Validation:** Index must be in range `1..task_count`.
|
|
189
|
-
- **Behavior:** Removes the top-level task line and any indented lines immediately following it (subtasks/notes).
|
|
190
|
-
|
|
191
|
-
## Error Handling
|
|
192
|
-
|
|
193
|
-
| Condition | Behavior |
|
|
194
|
-
|---|---|
|
|
195
|
-
| Unknown option | Exit 1, print message + `Run 'ralph --help'` |
|
|
196
|
-
| Invalid option value (bad agent, non-integer) | Exit 1, OptionParser error message + `Run 'ralph --help'` |
|
|
197
|
-
| No prompt provided | Exit 1, print usage hint |
|
|
198
|
-
| min-iterations > max-iterations | Exit 1, print constraint error |
|
|
199
|
-
| Agent CLI not found in PATH | Exit 1, print which agent is missing |
|
|
200
|
-
| Loop already active | Exit 1, print active loop info and state file path |
|
|
201
|
-
| Fatal error during loop | Exit 1, clear state, print error |
|
|
202
|
-
|
|
203
|
-
## Options Hash
|
|
5
|
+
## overview
|
|
204
6
|
|
|
205
|
-
|
|
7
|
+
Keep it simple stupid!! Unix is king, and we live by their philosophy
|
|
206
8
|
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
task_promise: String, # default "READY_FOR_NEXT_TASK"
|
|
215
|
-
model: String, # default ""
|
|
216
|
-
chosen_agent: String, # default "opencode"
|
|
217
|
-
auto_commit: Boolean, # default true
|
|
218
|
-
disable_plugins: Boolean, # default false
|
|
219
|
-
allow_all_permissions: Boolean, # default true
|
|
220
|
-
stream_output: Boolean, # default true
|
|
221
|
-
verbose_tools: Boolean, # default false
|
|
222
|
-
}
|
|
9
|
+
```sh
|
|
10
|
+
cat my-prompt-file.md | ralph "more prompt details" \
|
|
11
|
+
--model=opus-4.5 \
|
|
12
|
+
--max-iterations=10 \
|
|
13
|
+
--duration=10 \
|
|
14
|
+
--max-context=80000 \
|
|
15
|
+
--completion="<promise>COMPLETE</promise>"
|
|
223
16
|
```
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: Proprietary
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'time'
|
|
8
|
+
|
|
9
|
+
# Represents a single todo item with full feature support
|
|
10
|
+
class TodoItem
|
|
11
|
+
attr_reader :id, :title, :description, :created_at, :updated_at
|
|
12
|
+
attr_accessor :completed, :priority, :due_date, :tags, :category
|
|
13
|
+
|
|
14
|
+
PRIORITIES = { low: 1, medium: 2, high: 3, urgent: 4 }.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(title:, description: nil, priority: :medium, due_date: nil, tags: [], category: nil, id: nil)
|
|
17
|
+
@id = id || generate_id
|
|
18
|
+
@title = title
|
|
19
|
+
@description = description
|
|
20
|
+
@completed = false
|
|
21
|
+
@priority = priority
|
|
22
|
+
@due_date = due_date
|
|
23
|
+
@tags = tags || []
|
|
24
|
+
@category = category
|
|
25
|
+
@created_at = Time.now
|
|
26
|
+
@updated_at = Time.now
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def complete!
|
|
30
|
+
if @completed
|
|
31
|
+
@completed = false
|
|
32
|
+
else
|
|
33
|
+
@completed = true
|
|
34
|
+
end
|
|
35
|
+
@updated_at = Time.now
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def completed?
|
|
39
|
+
@completed
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def overdue?
|
|
43
|
+
if @due_date && !@completed
|
|
44
|
+
Time.parse(@due_date) < Time.now
|
|
45
|
+
else
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def due_soon?(hours: 24)
|
|
51
|
+
if @due_date && !@completed
|
|
52
|
+
due_time = Time.parse(@due_date)
|
|
53
|
+
due_time > Time.now && due_time < Time.now + (hours * 3600)
|
|
54
|
+
else
|
|
55
|
+
false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def priority_value
|
|
60
|
+
PRIORITIES[@priority] || PRIORITIES[:medium]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_tag(tag)
|
|
64
|
+
unless @tags.include?(tag)
|
|
65
|
+
@tags << tag
|
|
66
|
+
@updated_at = Time.now
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def remove_tag(tag)
|
|
71
|
+
if @tags.delete(tag)
|
|
72
|
+
@updated_at = Time.now
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def matches_filter?(query: nil, tags: nil, category: nil, completed: nil, priority: nil)
|
|
77
|
+
if query && !matches_query?(query)
|
|
78
|
+
false
|
|
79
|
+
elsif tags && (tags & @tags).empty?
|
|
80
|
+
false
|
|
81
|
+
elsif category && @category != category
|
|
82
|
+
false
|
|
83
|
+
elsif !completed.nil? && @completed != completed
|
|
84
|
+
false
|
|
85
|
+
elsif priority && @priority != priority
|
|
86
|
+
false
|
|
87
|
+
else
|
|
88
|
+
true
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_h
|
|
93
|
+
{
|
|
94
|
+
id: @id,
|
|
95
|
+
title: @title,
|
|
96
|
+
description: @description,
|
|
97
|
+
completed: @completed,
|
|
98
|
+
priority: @priority,
|
|
99
|
+
due_date: @due_date,
|
|
100
|
+
tags: @tags,
|
|
101
|
+
category: @category,
|
|
102
|
+
created_at: @created_at.iso8601,
|
|
103
|
+
updated_at: @updated_at.iso8601
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.from_h(hash)
|
|
108
|
+
new(
|
|
109
|
+
id: hash['id'],
|
|
110
|
+
title: hash['title'],
|
|
111
|
+
description: hash['description'],
|
|
112
|
+
priority: hash['priority']&.to_sym || :medium,
|
|
113
|
+
due_date: hash['due_date'],
|
|
114
|
+
tags: hash['tags'] || [],
|
|
115
|
+
category: hash['category']
|
|
116
|
+
).then do |item|
|
|
117
|
+
item.instance_variable_set(:@completed, hash['completed'] || false)
|
|
118
|
+
item.instance_variable_set(:@created_at, Time.parse(hash['created_at']))
|
|
119
|
+
item.instance_variable_set(:@updated_at, Time.parse(hash['updated_at']))
|
|
120
|
+
item
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def generate_id
|
|
127
|
+
"#{Time.now.to_i}-#{rand(10000)}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def matches_query?(query)
|
|
131
|
+
query_lower = query.downcase
|
|
132
|
+
if @title.downcase.include?(query_lower)
|
|
133
|
+
true
|
|
134
|
+
elsif @description && @description.downcase.include?(query_lower)
|
|
135
|
+
true
|
|
136
|
+
elsif @tags.any? { |tag| tag.downcase.include?(query_lower) }
|
|
137
|
+
true
|
|
138
|
+
elsif @category && @category.downcase.include?(query_lower)
|
|
139
|
+
true
|
|
140
|
+
else
|
|
141
|
+
false
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/specs/log
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{"type":"step_start","timestamp":1770187082911,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f4c89001ZPPQ0fLyoVyI6R","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"step-start","snapshot":"b743fc3bfaf58816da3d0e322f5af92274b3318c"}}
|
|
2
|
+
{"type":"text","timestamp":1770187082912,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f4c8b001bqAW435IjTqIWL","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"text","text":"I'll build you a comprehensive super todo app! Let me create a solid Ruby implementation with all the features you'd want.","time":{"start":1770187082902,"end":1770187082902}}}
|
|
3
|
+
{"type":"tool_use","timestamp":1770187087186,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f4c970017d4K2HeeM44TrY","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"tool","callID":"toolu_013ZVQg5YJHMYqKHav9rKXUu","tool":"todowrite","state":{"status":"completed","input":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"in_progress","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"pending","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}]},"output":"[\n {\n \"id\": \"1\",\n \"content\": \"Design the todo app architecture and data model\",\n \"status\": \"in_progress\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"2\",\n \"content\": \"Implement core Todo and TodoList classes\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"3\",\n \"content\": \"Add persistence layer (JSON file storage)\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"4\",\n \"content\": \"Create CLI interface with command handling\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"5\",\n \"content\": \"Add filtering and search capabilities\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"6\",\n \"content\": \"Implement priority levels and due dates\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"7\",\n \"content\": \"Add tags and categories\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"8\",\n \"content\": \"Create pretty output formatting\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"9\",\n \"content\": \"Write comprehensive tests\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"10\",\n \"content\": \"Add README with usage examples\",\n \"status\": \"pending\",\n \"priority\": \"low\"\n }\n]","title":"10 todos","metadata":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"in_progress","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"pending","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}],"truncated":false},"time":{"start":1770187087172,"end":1770187087180}}}}
|
|
4
|
+
{"type":"step_finish","timestamp":1770187087202,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f5d51001K0X6r5is6bSpCF","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"step-finish","reason":"tool-calls","snapshot":"a57194ef5a651225fcd30a8c1693512e5ac64d59","cost":0,"tokens":{"input":2,"output":392,"reasoning":0,"cache":{"read":0,"write":15463}}}}
|
|
5
|
+
{"type":"step_start","timestamp":1770187100723,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9230001va5bdY1KhjxQG0","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"step-start","snapshot":"cdd7b1abdd3f43bd9efb116cffba98955ba57a2e"}}
|
|
6
|
+
{"type":"tool_use","timestamp":1770187100734,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9234001Uu930gEHD2BlmN","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"tool","callID":"toolu_01EYZZEC8oyTLrULZrUsUEx2","tool":"read","state":{"status":"completed","input":{"filePath":"/home/nathan/src/ralph.rb/specs/README.md"},"output":"<file>\n00001| <!--\n00002| Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.\n00003| SPDX-License-Identifier: Proprietary\n00004| -->\n00005| \n00006| <!--\n00007| HOW TO MAINTAIN THIS FILE\n00008| \n00009| This is the index of all design specifications for Ralph.rb.\n00010| Each row links a spec document to its implementation code and a short purpose summary.\n00011| \n00012| When adding a new spec:\n00013| 1. Create the markdown file in this directory (specs/)\n00014| 2. Add a row to the appropriate table section below\n00015| 3. Link the spec file, the code path it describes, and a brief purpose\n00016| \n00017| Table format:\n00018| | [spec-name.md](./spec-name.md) | [path/to/code](../path/to/code) | Short description |\n00019| \n00020| Use \"—\" in the Code column if the spec has no implementation yet.\n00021| Group specs under heading sections by domain area.\n00022| -->\n00023| \n00024| # Ralph.rb Specifications\n00025| \n00026| Design documentation for Ralph.rb, a Ruby CLI that runs iterative AI development loops.\n00027| \n00028| ## Core\n00029| \n00030| | Spec | Code | Purpose |\n00031| |------|------|---------|\n00032| \n\n(End of file - total 32 lines)\n</file>","title":"specs/README.md","metadata":{"preview":"<!--\n Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.\n SPDX-License-Identifier: Proprietary\n-->\n\n<!--\n HOW TO MAINTAIN THIS FILE\n\n This is the index of all design specifications for Ralph.rb.\n Each row links a spec document to its implementation code and a short purpose summary.\n\n When adding a new spec:\n 1. Create the markdown file in this directory (specs/)\n 2. Add a row to the appropriate table section below\n 3. Link the spec file, the code path it describes, and a brief purpose\n\n Table format:\n | [spec-name.md](./spec-name.md) | [path/to/code](../path/to/code) | Short description |\n\n Use \"—\" in the Code column if the spec has no implementation yet.","truncated":false},"time":{"start":1770187100725,"end":1770187100727}}}}
|
|
7
|
+
{"type":"tool_use","timestamp":1770187100734,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f92310016vUTiBTRHwDM5W","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"tool","callID":"toolu_013K7yZnRe7rxGp6McUPRv6c","tool":"glob","state":{"status":"completed","input":{"pattern":"**/*.rb"},"output":"No files found","title":"specs","metadata":{"count":0,"truncated":false},"time":{"start":1770187100722,"end":1770187100727}}}}
|
|
8
|
+
{"type":"step_finish","timestamp":1770187100738,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9238001Bc7W61klSlCgMS","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"step-finish","reason":"tool-calls","snapshot":"9ff571000c58ef49757c25c596933e95d027b68a","cost":0,"tokens":{"input":5,"output":109,"reasoning":0,"cache":{"read":15463,"write":820}}}}
|
|
9
|
+
{"type":"step_start","timestamp":1770187103298,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9c3f00174ayQ55QZMnCx1","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f9253001aLp1tmXMfkYWJP","type":"step-start","snapshot":"11b5b96ce076193e7d1845670a5bf54dc1dd710e"}}
|
|
10
|
+
{"type":"tool_use","timestamp":1770187105724,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9c7100124pI64UBSSh46E","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f9253001aLp1tmXMfkYWJP","type":"tool","callID":"toolu_01FraqehJ4VqbchH6Yg9xCyB","tool":"todowrite","state":{"status":"completed","input":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"completed","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"in_progress","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}]},"output":"[\n {\n \"id\": \"1\",\n \"content\": \"Design the todo app architecture and data model\",\n \"status\": \"completed\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"2\",\n \"content\": \"Implement core Todo and TodoList classes\",\n \"status\": \"in_progress\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"3\",\n \"content\": \"Add persistence layer (JSON file storage)\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"4\",\n \"content\": \"Create CLI interface with command handling\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"5\",\n \"content\": \"Add filtering and search capabilities\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"6\",\n \"content\": \"Implement priority levels and due dates\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"7\",\n \"content\": \"Add tags and categories\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"8\",\n \"content\": \"Create pretty output formatting\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"9\",\n \"content\": \"Write comprehensive tests\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"10\",\n \"content\": \"Add README with usage examples\",\n \"status\": \"pending\",\n \"priority\": \"low\"\n }\n]","title":"9 todos","metadata":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"completed","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"in_progress","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}],"truncated":false},"time":{"start":1770187105711,"end":1770187105720}}}}
|
|
11
|
+
{"type":"step_finish","timestamp":1770187105737,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275fa5ba001LZse64oqhRnWw6","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f9253001aLp1tmXMfkYWJP","type":"step-finish","reason":"tool-calls","snapshot":"0012aee2405832cd50eb58cf61f9d6a06d5ec8d7","cost":0,"tokens":{"input":6,"output":354,"reasoning":0,"cache":{"read":16283,"write":575}}}}
|
|
12
|
+
{"type":"step_start","timestamp":1770187107336,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275fac07001OyYeVzom3wXWKJ","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275fa5de001g6TZgrYArTMYDj","type":"step-start","snapshot":"6a96baea47fc3070e5cca8f349aa76d459448b35"}}
|
|
13
|
+
{"type":"tool_use","timestamp":1770187123469,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275fac08001jiLorarTEUEKFX","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275fa5de001g6TZgrYArTMYDj","type":"tool","callID":"toolu_014EWUsB3p8FPzhZJEW2HWPx","tool":"write","state":{"status":"completed","input":{"filePath":"/home/nathan/src/ralph.rb/specs/lib/todo_item.rb","content":"# frozen_string_literal: true\n\n# Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.\n# SPDX-License-Identifier: Proprietary\n\nrequire 'json'\nrequire 'time'\n\n# Represents a single todo item with full feature support\nclass TodoItem\n attr_reader :id, :title, :description, :created_at, :updated_at\n attr_accessor :completed, :priority, :due_date, :tags, :category\n\n PRIORITIES = { low: 1, medium: 2, high: 3, urgent: 4 }.freeze\n\n def initialize(title:, description: nil, priority: :medium, due_date: nil, tags: [], category: nil, id: nil)\n @id = id || generate_id\n @title = title\n @description = description\n @completed = false\n @priority = priority\n @due_date = due_date\n @tags = tags || []\n @category = category\n @created_at = Time.now\n @updated_at = Time.now\n end\n\n def complete!\n if @completed\n @completed = false\n else\n @completed = true\n end\n @updated_at = Time.now\n end\n\n def completed?\n @completed\n end\n\n def overdue?\n if @due_date && !@completed\n Time.parse(@due_date) < Time.now\n else\n false\n end\n end\n\n def due_soon?(hours: 24)\n if @due_date && !@completed\n due_time = Time.parse(@due_date)\n due_time > Time.now && due_time < Time.now + (hours * 3600)\n else\n false\n end\n end\n\n def priority_value\n PRIORITIES[@priority] || PRIORITIES[:medium]\n end\n\n def add_tag(tag)\n unless @tags.include?(tag)\n @tags << tag\n @updated_at = Time.now\n end\n end\n\n def remove_tag(tag)\n if @tags.delete(tag)\n @updated_at = Time.now\n end\n end\n\n def matches_filter?(query: nil, tags: nil, category: nil, completed: nil, priority: nil)\n if query && !matches_query?(query)\n false\n elsif tags && (tags & @tags).empty?\n false\n elsif category && @category != category\n false\n elsif !completed.nil? && @completed != completed\n false\n elsif priority && @priority != priority\n false\n else\n true\n end\n end\n\n def to_h\n {\n id: @id,\n title: @title,\n description: @description,\n completed: @completed,\n priority: @priority,\n due_date: @due_date,\n tags: @tags,\n category: @category,\n created_at: @created_at.iso8601,\n updated_at: @updated_at.iso8601\n }\n end\n\n def self.from_h(hash)\n new(\n id: hash['id'],\n title: hash['title'],\n description: hash['description'],\n priority: hash['priority']&.to_sym || :medium,\n due_date: hash['due_date'],\n tags: hash['tags'] || [],\n category: hash['category']\n ).then do |item|\n item.instance_variable_set(:@completed, hash['completed'] || false)\n item.instance_variable_set(:@created_at, Time.parse(hash['created_at']))\n item.instance_variable_set(:@updated_at, Time.parse(hash['updated_at']))\n item\n end\n end\n\n private\n\n def generate_id\n \"#{Time.now.to_i}-#{rand(10000)}\"\n end\n\n def matches_query?(query)\n query_lower = query.downcase\n if @title.downcase.include?(query_lower)\n true\n elsif @description && @description.downcase.include?(query_lower)\n true\n elsif @tags.any? { |tag| tag.downcase.include?(query_lower) }\n true\n elsif @category && @category.downcase.include?(query_lower)\n true\n else\n false\n end\n end\nend\n"},"output":"","title":"specs/lib/todo_item.rb","metadata":{"diagnostics":{"/home/nathan/src/ralph.rb/specs/lib/todo_item.rb":[]},"filepath":"/home/nathan/src/ralph.rb/specs/lib/todo_item.rb","exists":false,"truncated":false},"time":{"start":1770187120035,"end":1770187123465}}}}
|
|
14
|
+
{"type":"step_finish","timestamp":1770187123482,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275feb0b001BNSMKEYbJq0b91","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275fa5de001g6TZgrYArTMYDj","type":"step-finish","reason":"tool-calls","snapshot":"177366b69da91d17a347fe0ad0f65091e4bea58d","cost":0,"tokens":{"input":5,"output":1164,"reasoning":0,"cache":{"read":16858,"write":798}}}}
|
|
15
|
+
{"type":"step_start","timestamp":1770187125160,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275ff1a60017BYnKNMjfD21UA","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275feb31001x6mnGGa2J1HWi5","type":"step-start","snapshot":"9edefc0011373084034390a13e04d98067bc123b"}}
|
data/specs/loop.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Loop Specification
|
|
2
|
+
|
|
3
|
+
What is a ralph loop, and how do we implement one?
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The essence of a ralph loop is an infinite loop will certain guards to prevent context becoming tainted. If the context becomes too large then then the iteration is cancelled and started again. The work done is not lost, it just means that a fresh context will carry on the work.
|
|
8
|
+
|
|
9
|
+
This whole technique relies on a way of constantly reminding the agent what it's working on and what it's done. This is simply ensured by a task list. It will be passed the task list as a prompt, and will work until it thinkgs it has completed the task, or it is cancelled by the loop.
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
A main loop simply runs contonuously to read data from opencode, and check whether the iteration needs cancelling.
|
|
14
|
+
|
|
15
|
+
An iteration object is created which run the agent, and can be cancelled whenever we like.
|
|
16
|
+
|
|
17
|
+
In order for the loop to stop once complete, we must instruct the agent to pass back a certain string that we will detect when it thinks it has completed.
|
|
18
|
+
|
|
19
|
+
### What ends the loop iteration
|
|
20
|
+
- iteration context length
|
|
21
|
+
- iteration duration
|
|
22
|
+
|
|
23
|
+
### What ends the main loop
|
|
24
|
+
- complete string received
|
|
25
|
+
- max iteration reached
|
|
26
|
+
- loop duration
|
|
27
|
+
|
|
28
|
+
## Prompts
|
|
29
|
+
Only prompt needed is to explain to return the string once completed, or perhaps to follow the items on the list. The rest can be passed in by the user.
|
|
30
|
+
|
|
31
|
+
**important** WE MUST INSTRUCT the agent NOT to interact with the user. All questions must be aswered by reading the spec.
|
|
32
|
+
|
|
33
|
+
## Display Output
|
|
34
|
+
|
|
35
|
+
We want to see the following:
|
|
36
|
+
- number of iterations
|
|
37
|
+
- current iteration
|
|
38
|
+
- duration
|
|
39
|
+
- tokens consumed
|
|
40
|
+
- over all and per iteration data
|
|
41
|
+
- the output of the agent
|
|
42
|
+
- the input prompt
|
data/specs/metrics.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Metrics specification
|
|
2
|
+
|
|
3
|
+
How will we get essential data such as current context?
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
JSON streaming allows us to do this.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
Here's an example JSON stream from opencode. You will read the stream and parse the context from it so that the loop can perform it's checks.
|
|
12
|
+
An Events.rb object will contain classes representing each event we need to parse.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
{"type":"step_start","timestamp":1770187082911,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f4c89001ZPPQ0fLyoVyI6R","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"step-start","snapshot":"b743fc3bfaf58816da3d0e322f5af92274b3318c"}}
|
|
16
|
+
{"type":"text","timestamp":1770187082912,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f4c8b001bqAW435IjTqIWL","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"text","text":"I'll build you a comprehensive super todo app! Let me create a solid Ruby implementation with all the features you'd want.","time":{"start":1770187082902,"end":1770187082902}}}
|
|
17
|
+
{"type":"tool_use","timestamp":1770187087186,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f4c970017d4K2HeeM44TrY","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"tool","callID":"toolu_013ZVQg5YJHMYqKHav9rKXUu","tool":"todowrite","state":{"status":"completed","input":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"in_progress","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"pending","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}]},"output":"[\n {\n \"id\": \"1\",\n \"content\": \"Design the todo app architecture and data model\",\n \"status\": \"in_progress\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"2\",\n \"content\": \"Implement core Todo and TodoList classes\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"3\",\n \"content\": \"Add persistence layer (JSON file storage)\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"4\",\n \"content\": \"Create CLI interface with command handling\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"5\",\n \"content\": \"Add filtering and search capabilities\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"6\",\n \"content\": \"Implement priority levels and due dates\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"7\",\n \"content\": \"Add tags and categories\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"8\",\n \"content\": \"Create pretty output formatting\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"9\",\n \"content\": \"Write comprehensive tests\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"10\",\n \"content\": \"Add README with usage examples\",\n \"status\": \"pending\",\n \"priority\": \"low\"\n }\n]","title":"10 todos","metadata":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"in_progress","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"pending","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}],"truncated":false},"time":{"start":1770187087172,"end":1770187087180}}}}
|
|
18
|
+
{"type":"step_finish","timestamp":1770187087202,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f5d51001K0X6r5is6bSpCF","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f17af001hBx2ZnI5s2T7X6","type":"step-finish","reason":"tool-calls","snapshot":"a57194ef5a651225fcd30a8c1693512e5ac64d59","cost":0,"tokens":{"input":2,"output":392,"reasoning":0,"cache":{"read":0,"write":15463}}}}
|
|
19
|
+
{"type":"step_start","timestamp":1770187100723,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9230001va5bdY1KhjxQG0","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"step-start","snapshot":"cdd7b1abdd3f43bd9efb116cffba98955ba57a2e"}}
|
|
20
|
+
{"type":"tool_use","timestamp":1770187100734,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9234001Uu930gEHD2BlmN","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"tool","callID":"toolu_01EYZZEC8oyTLrULZrUsUEx2","tool":"read","state":{"status":"completed","input":{"filePath":"/home/nathan/src/ralph.rb/specs/README.md"},"output":"<file>\n00001| <!--\n00002| Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.\n00003| SPDX-License-Identifier: Proprietary\n00004| -->\n00005| \n00006| <!--\n00007| HOW TO MAINTAIN THIS FILE\n00008| \n00009| This is the index of all design specifications for Ralph.rb.\n00010| Each row links a spec document to its implementation code and a short purpose summary.\n00011| \n00012| When adding a new spec:\n00013| 1. Create the markdown file in this directory (specs/)\n00014| 2. Add a row to the appropriate table section below\n00015| 3. Link the spec file, the code path it describes, and a brief purpose\n00016| \n00017| Table format:\n00018| | [spec-name.md](./spec-name.md) | [path/to/code](../path/to/code) | Short description |\n00019| \n00020| Use \"—\" in the Code column if the spec has no implementation yet.\n00021| Group specs under heading sections by domain area.\n00022| -->\n00023| \n00024| # Ralph.rb Specifications\n00025| \n00026| Design documentation for Ralph.rb, a Ruby CLI that runs iterative AI development loops.\n00027| \n00028| ## Core\n00029| \n00030| | Spec | Code | Purpose |\n00031| |------|------|---------|\n00032| \n\n(End of file - total 32 lines)\n</file>","title":"specs/README.md","metadata":{"preview":"<!--\n Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.\n SPDX-License-Identifier: Proprietary\n-->\n\n<!--\n HOW TO MAINTAIN THIS FILE\n\n This is the index of all design specifications for Ralph.rb.\n Each row links a spec document to its implementation code and a short purpose summary.\n\n When adding a new spec:\n 1. Create the markdown file in this directory (specs/)\n 2. Add a row to the appropriate table section below\n 3. Link the spec file, the code path it describes, and a brief purpose\n\n Table format:\n | [spec-name.md](./spec-name.md) | [path/to/code](../path/to/code) | Short description |\n\n Use \"—\" in the Code column if the spec has no implementation yet.","truncated":false},"time":{"start":1770187100725,"end":1770187100727}}}}
|
|
21
|
+
{"type":"tool_use","timestamp":1770187100734,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f92310016vUTiBTRHwDM5W","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"tool","callID":"toolu_013K7yZnRe7rxGp6McUPRv6c","tool":"glob","state":{"status":"completed","input":{"pattern":"**/*.rb"},"output":"No files found","title":"specs","metadata":{"count":0,"truncated":false},"time":{"start":1770187100722,"end":1770187100727}}}}
|
|
22
|
+
{"type":"step_finish","timestamp":1770187100738,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9238001Bc7W61klSlCgMS","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f5d7a001ahslHtCQnw7J1X","type":"step-finish","reason":"tool-calls","snapshot":"9ff571000c58ef49757c25c596933e95d027b68a","cost":0,"tokens":{"input":5,"output":109,"reasoning":0,"cache":{"read":15463,"write":820}}}}
|
|
23
|
+
{"type":"step_start","timestamp":1770187103298,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9c3f00174ayQ55QZMnCx1","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f9253001aLp1tmXMfkYWJP","type":"step-start","snapshot":"11b5b96ce076193e7d1845670a5bf54dc1dd710e"}}
|
|
24
|
+
{"type":"tool_use","timestamp":1770187105724,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275f9c7100124pI64UBSSh46E","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f9253001aLp1tmXMfkYWJP","type":"tool","callID":"toolu_01FraqehJ4VqbchH6Yg9xCyB","tool":"todowrite","state":{"status":"completed","input":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"completed","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"in_progress","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}]},"output":"[\n {\n \"id\": \"1\",\n \"content\": \"Design the todo app architecture and data model\",\n \"status\": \"completed\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"2\",\n \"content\": \"Implement core Todo and TodoList classes\",\n \"status\": \"in_progress\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"3\",\n \"content\": \"Add persistence layer (JSON file storage)\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"4\",\n \"content\": \"Create CLI interface with command handling\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"5\",\n \"content\": \"Add filtering and search capabilities\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"6\",\n \"content\": \"Implement priority levels and due dates\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"7\",\n \"content\": \"Add tags and categories\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"8\",\n \"content\": \"Create pretty output formatting\",\n \"status\": \"pending\",\n \"priority\": \"medium\"\n },\n {\n \"id\": \"9\",\n \"content\": \"Write comprehensive tests\",\n \"status\": \"pending\",\n \"priority\": \"high\"\n },\n {\n \"id\": \"10\",\n \"content\": \"Add README with usage examples\",\n \"status\": \"pending\",\n \"priority\": \"low\"\n }\n]","title":"9 todos","metadata":{"todos":[{"id":"1","content":"Design the todo app architecture and data model","status":"completed","priority":"high"},{"id":"2","content":"Implement core Todo and TodoList classes","status":"in_progress","priority":"high"},{"id":"3","content":"Add persistence layer (JSON file storage)","status":"pending","priority":"high"},{"id":"4","content":"Create CLI interface with command handling","status":"pending","priority":"high"},{"id":"5","content":"Add filtering and search capabilities","status":"pending","priority":"medium"},{"id":"6","content":"Implement priority levels and due dates","status":"pending","priority":"medium"},{"id":"7","content":"Add tags and categories","status":"pending","priority":"medium"},{"id":"8","content":"Create pretty output formatting","status":"pending","priority":"medium"},{"id":"9","content":"Write comprehensive tests","status":"pending","priority":"high"},{"id":"10","content":"Add README with usage examples","status":"pending","priority":"low"}],"truncated":false},"time":{"start":1770187105711,"end":1770187105720}}}}
|
|
25
|
+
{"type":"step_finish","timestamp":1770187105737,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275fa5ba001LZse64oqhRnWw6","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275f9253001aLp1tmXMfkYWJP","type":"step-finish","reason":"tool-calls","snapshot":"0012aee2405832cd50eb58cf61f9d6a06d5ec8d7","cost":0,"tokens":{"input":6,"output":354,"reasoning":0,"cache":{"read":16283,"write":575}}}}
|
|
26
|
+
{"type":"step_start","timestamp":1770187107336,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275fac07001OyYeVzom3wXWKJ","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275fa5de001g6TZgrYArTMYDj","type":"step-start","snapshot":"6a96baea47fc3070e5cca8f349aa76d459448b35"}}
|
|
27
|
+
{"type":"tool_use","timestamp":1770187123469,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275fac08001jiLorarTEUEKFX","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275fa5de001g6TZgrYArTMYDj","type":"tool","callID":"toolu_014EWUsB3p8FPzhZJEW2HWPx","tool":"write","state":{"status":"completed","input":{"filePath":"/home/nathan/src/ralph.rb/specs/lib/todo_item.rb","content":"# frozen_string_literal: true\n\n# Copyright (c) 2025 Nathan Kidd <nathankidd@hey.com>. All rights reserved.\n# SPDX-License-Identifier: Proprietary\n\nrequire 'json'\nrequire 'time'\n\n# Represents a single todo item with full feature support\nclass TodoItem\n attr_reader :id, :title, :description, :created_at, :updated_at\n attr_accessor :completed, :priority, :due_date, :tags, :category\n\n PRIORITIES = { low: 1, medium: 2, high: 3, urgent: 4 }.freeze\n\n def initialize(title:, description: nil, priority: :medium, due_date: nil, tags: [], category: nil, id: nil)\n @id = id || generate_id\n @title = title\n @description = description\n @completed = false\n @priority = priority\n @due_date = due_date\n @tags = tags || []\n @category = category\n @created_at = Time.now\n @updated_at = Time.now\n end\n\n def complete!\n if @completed\n @completed = false\n else\n @completed = true\n end\n @updated_at = Time.now\n end\n\n def completed?\n @completed\n end\n\n def overdue?\n if @due_date && !@completed\n Time.parse(@due_date) < Time.now\n else\n false\n end\n end\n\n def due_soon?(hours: 24)\n if @due_date && !@completed\n due_time = Time.parse(@due_date)\n due_time > Time.now && due_time < Time.now + (hours * 3600)\n else\n false\n end\n end\n\n def priority_value\n PRIORITIES[@priority] || PRIORITIES[:medium]\n end\n\n def add_tag(tag)\n unless @tags.include?(tag)\n @tags << tag\n @updated_at = Time.now\n end\n end\n\n def remove_tag(tag)\n if @tags.delete(tag)\n @updated_at = Time.now\n end\n end\n\n def matches_filter?(query: nil, tags: nil, category: nil, completed: nil, priority: nil)\n if query && !matches_query?(query)\n false\n elsif tags && (tags & @tags).empty?\n false\n elsif category && @category != category\n false\n elsif !completed.nil? && @completed != completed\n false\n elsif priority && @priority != priority\n false\n else\n true\n end\n end\n\n def to_h\n {\n id: @id,\n title: @title,\n description: @description,\n completed: @completed,\n priority: @priority,\n due_date: @due_date,\n tags: @tags,\n category: @category,\n created_at: @created_at.iso8601,\n updated_at: @updated_at.iso8601\n }\n end\n\n def self.from_h(hash)\n new(\n id: hash['id'],\n title: hash['title'],\n description: hash['description'],\n priority: hash['priority']&.to_sym || :medium,\n due_date: hash['due_date'],\n tags: hash['tags'] || [],\n category: hash['category']\n ).then do |item|\n item.instance_variable_set(:@completed, hash['completed'] || false)\n item.instance_variable_set(:@created_at, Time.parse(hash['created_at']))\n item.instance_variable_set(:@updated_at, Time.parse(hash['updated_at']))\n item\n end\n end\n\n private\n\n def generate_id\n \"#{Time.now.to_i}-#{rand(10000)}\"\n end\n\n def matches_query?(query)\n query_lower = query.downcase\n if @title.downcase.include?(query_lower)\n true\n elsif @description && @description.downcase.include?(query_lower)\n true\n elsif @tags.any? { |tag| tag.downcase.include?(query_lower) }\n true\n elsif @category && @category.downcase.include?(query_lower)\n true\n else\n false\n end\n end\nend\n"},"output":"","title":"specs/lib/todo_item.rb","metadata":{"diagnostics":{"/home/nathan/src/ralph.rb/specs/lib/todo_item.rb":[]},"filepath":"/home/nathan/src/ralph.rb/specs/lib/todo_item.rb","exists":false,"truncated":false},"time":{"start":1770187120035,"end":1770187123465}}}}
|
|
28
|
+
{"type":"step_finish","timestamp":1770187123482,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275feb0b001BNSMKEYbJq0b91","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275fa5de001g6TZgrYArTMYDj","type":"step-finish","reason":"tool-calls","snapshot":"177366b69da91d17a347fe0ad0f65091e4bea58d","cost":0,"tokens":{"input":5,"output":1164,"reasoning":0,"cache":{"read":16858,"write":798}}}}
|
|
29
|
+
{"type":"step_start","timestamp":1770187125160,"sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","part":{"id":"prt_c275ff1a60017BYnKNMjfD21UA","sessionID":"ses_3d8a0e8abffesEdbo6QsvtRrNS","messageID":"msg_c275feb31001x6mnGGa2J1HWi5","type":"step-start","snapshot":"9edefc0011373084034390a13e04d98067bc123b"}}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## calculation
|
|
33
|
+
|
|
34
|
+
The context size lives in the `step_finish` events, in the `tokens` field. The total input context for any step is:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
context = input + cache.read + cache.write
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Walking through your log:
|
|
41
|
+
|
|
42
|
+
| Step | input | cache.read | cache.write | **Total Context** |
|
|
43
|
+
|------|-------|------------|-------------|-------------------|
|
|
44
|
+
| 1 | 2 | 0 | 15,463 | **15,465** |
|
|
45
|
+
| 2 | 5 | 15,463 | 820 | **16,288** |
|
|
46
|
+
| 3 | 6 | 16,283 | 575 | **16,864** |
|
|
47
|
+
| 4 | 5 | 16,858 | 798 | **17,661** |
|
|
48
|
+
|
|
49
|
+
The pattern: each step's `cache.read` ≈ previous step's `cache.read + cache.write`, meaning the previously-seen context gets cached. The `cache.write` is the new tokens added that step (tool results, assistant output). `input` is the tiny uncacheable portion (likely the few tokens at a cache boundary).
|
|
50
|
+
|
|
51
|
+
So to get the current context window usage at any point, sum all three input token fields from the most recent `step_finish`. To track growth over time, chart that sum across steps — you can see context growing ~400-1200 tokens per step here as tool results accumulate.
|