Skip to content

Commit 0db92a9

Browse files
Kasper Jungeclaude
authored andcommitted
docs: clarify live-editing behavior in dashboard and add docstrings to RunManager
Users editing primitives via the Configure tab while a run is active would expect changes to take effect immediately. Added a table to the dashboard docs explaining what's hot-reloadable (prompt, context output) vs what requires a restart (primitive config, new/deleted primitives). Also added docstrings to all RunManager methods and RunState control methods so contributors and AI agents can understand the orchestration layer without reading implementation details. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0c918a6 commit 0db92a9

3 files changed

Lines changed: 36 additions & 0 deletions

File tree

docs/dashboard.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ Click **New Run** in the sidebar to open the run modal. It lets you:
115115
Once a run starts, you can **pause**, **resume**, or **stop** it from the
116116
sidebar or the controls bar above the Timeline.
117117

118+
## Editing while a run is active
119+
120+
The dashboard lets you edit primitives via the Configure tab while a run is in progress, but not all changes take effect immediately:
121+
122+
| What you change | When it takes effect |
123+
|---|---|
124+
| `PROMPT.md` content | Next iteration — the prompt is re-read from disk every iteration |
125+
| Context command output | Next iteration — context commands re-run every iteration |
126+
| Check/context/instruction config (frontmatter, body, enable/disable) | After restart — primitive configurations are loaded once when a run starts |
127+
| New or deleted primitives | After restart — primitive discovery happens once at run start |
128+
129+
To apply primitive configuration changes to a running run, **stop** the run and **start a new one**. The new run will discover the updated primitives from disk.
130+
131+
!!! tip "Prompt edits are always live"
132+
The most common adjustment — adding a constraint or changing the task in your prompt — takes effect on the very next iteration without restarting. This is the primary way to steer the agent in real time.
133+
118134
## Architecture
119135

120136
The dashboard is a single-page app that talks to a FastAPI backend:

src/ralphify/engine.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,23 @@ def __post_init__(self) -> None:
8080
self._pause_event.set()
8181

8282
def request_stop(self) -> None:
83+
"""Signal the loop to stop after the current iteration."""
8384
self._stop_requested = True
8485
# Unpause so the loop can exit
8586
self._pause_event.set()
8687

8788
def request_pause(self) -> None:
89+
"""Pause the loop between iterations until resumed."""
8890
self.status = RunStatus.PAUSED
8991
self._pause_event.clear()
9092

9193
def request_resume(self) -> None:
94+
"""Resume a paused loop."""
9295
self.status = RunStatus.RUNNING
9396
self._pause_event.set()
9497

9598
def request_reload(self) -> None:
99+
"""Request re-discovery of primitives before the next iteration."""
96100
self._reload_requested = True
97101

98102

src/ralphify/manager.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class ManagedRun:
2424
_extra_emitters: list[EventEmitter] = field(default_factory=list)
2525

2626
def add_listener(self, emitter: EventEmitter) -> None:
27+
"""Register an additional emitter to receive events from this run."""
2728
self._extra_emitters.append(emitter)
2829

2930

@@ -46,6 +47,11 @@ def __init__(self) -> None:
4647
self._lock = threading.Lock()
4748

4849
def create_run(self, config: RunConfig) -> ManagedRun:
50+
"""Create a new run from *config* and register it.
51+
52+
Assigns a unique run ID and returns the :class:`ManagedRun`.
53+
The run is not started — call :meth:`start_run` to begin execution.
54+
"""
4955
run_id = uuid.uuid4().hex[:12]
5056
state = RunState(run_id=run_id)
5157
emitter = QueueEmitter()
@@ -55,6 +61,11 @@ def create_run(self, config: RunConfig) -> ManagedRun:
5561
return managed
5662

5763
def start_run(self, run_id: str) -> None:
64+
"""Start the run loop in a daemon thread.
65+
66+
The thread calls :func:`engine.run_loop` with a fanout emitter
67+
that broadcasts events to the run's queue and any extra listeners.
68+
"""
5869
with self._lock:
5970
managed = self._runs[run_id]
6071
all_emitters: list[EventEmitter] = [managed.emitter] + managed._extra_emitters
@@ -69,24 +80,29 @@ def start_run(self, run_id: str) -> None:
6980
thread.start()
7081

7182
def stop_run(self, run_id: str) -> None:
83+
"""Signal the run to stop after the current iteration finishes."""
7284
with self._lock:
7385
managed = self._runs[run_id]
7486
managed.state.request_stop()
7587

7688
def pause_run(self, run_id: str) -> None:
89+
"""Pause the run between iterations until :meth:`resume_run` is called."""
7790
with self._lock:
7891
managed = self._runs[run_id]
7992
managed.state.request_pause()
8093

8194
def resume_run(self, run_id: str) -> None:
95+
"""Resume a paused run."""
8296
with self._lock:
8397
managed = self._runs[run_id]
8498
managed.state.request_resume()
8599

86100
def list_runs(self) -> list[ManagedRun]:
101+
"""Return a snapshot of all registered runs."""
87102
with self._lock:
88103
return list(self._runs.values())
89104

90105
def get_run(self, run_id: str) -> ManagedRun | None:
106+
"""Look up a run by ID, returning ``None`` if not found."""
91107
with self._lock:
92108
return self._runs.get(run_id)

0 commit comments

Comments
 (0)