Skip to content

Commit 84ee12a

Browse files
committed
Sync docs, sample bundle, and audit fixes
1 parent 883198a commit 84ee12a

73 files changed

Lines changed: 4794 additions & 169 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ models/*
5252
node_modules/
5353
output/*
5454
!output/README.md
55+
!output/examples/
56+
output/examples/*
57+
!output/examples/real_e2e_example_run/
58+
!output/examples/real_e2e_example_run/manifest.json
59+
!output/examples/real_e2e_example_run/real_e2e_example_run.html
60+
!output/examples/real_e2e_example_run/session_trace_report.html
61+
!output/examples/real_e2e_example_run/images/
62+
!output/examples/real_e2e_example_run/images/*.png
63+
output/examples/real_e2e_example_run/data/
5564
tmp/
5665
temp/
5766
tmp_real_smoke/

README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,34 @@ The current system includes:
5959
- browser and backend test coverage
6060
- a real GPU-backed example-run generator with standalone HTML output
6161

62+
Example artifacts checked into the repo:
63+
64+
- [Sample HTML walkthrough](./output/examples/real_e2e_example_run/real_e2e_example_run.html)
65+
- [Sample trace report](./output/examples/real_e2e_example_run/session_trace_report.html)
66+
- [Sample manifest](./output/examples/real_e2e_example_run/manifest.json)
67+
6268
## User Flow
6369

6470
The main workflow is prompt-first:
6571

6672
1. the user opens `/setup`
6773
2. enters a text prompt
68-
3. creates a session
69-
4. generates a round of candidate images
70-
5. submits ratings or preferences
71-
6. waits for the async update job to finish
72-
7. inspects replay and the saved trace report
74+
3. optionally edits the per-session YAML configuration
75+
4. creates a session
76+
5. generates a round of candidate images
77+
6. submits explicit feedback for the active mode
78+
7. waits for the async update job to finish
79+
8. inspects replay and the saved trace report
7380

7481
The normal runtime is GPU-only and uses the real Diffusers backend. If CUDA is unavailable, the app refuses to start instead of silently falling back.
7582

83+
![Runtime architecture diagram](./docs/assets/illustrations/runtime_flow.svg)
84+
7685
## Getting Started
7786

7887
Install the project:
7988

8089
```bash
81-
python -m pip install -e .[dev]
8290
python -m pip install -e .[dev,inference]
8391
```
8492

@@ -163,6 +171,11 @@ Real end-to-end example bundle:
163171
python scripts/create_real_e2e_example.py
164172
```
165173

174+
Checked-in sample bundle:
175+
176+
- [real_e2e_example_run.html](./output/examples/real_e2e_example_run/real_e2e_example_run.html)
177+
- [session_trace_report.html](./output/examples/real_e2e_example_run/session_trace_report.html)
178+
166179
## Repo Guides
167180

168181
Per-folder documentation is available in:
@@ -197,6 +210,10 @@ Current visual assets include:
197210
- [docs/assets/illustrations/steering_loop.png](./docs/assets/illustrations/steering_loop.png)
198211
- [docs/assets/illustrations/system_architecture.png](./docs/assets/illustrations/system_architecture.png)
199212
- [docs/assets/illustrations/trace_report.png](./docs/assets/illustrations/trace_report.png)
213+
- [docs/assets/illustrations/runtime_flow.svg](./docs/assets/illustrations/runtime_flow.svg)
214+
- [docs/assets/illustrations/session_lifecycle.svg](./docs/assets/illustrations/session_lifecycle.svg)
215+
- [docs/assets/illustrations/feedback_modes.svg](./docs/assets/illustrations/feedback_modes.svg)
216+
- [docs/assets/illustrations/config_to_generation.svg](./docs/assets/illustrations/config_to_generation.svg)
200217

201218
They can be regenerated with:
202219

app/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ This folder contains the running StableSteering application.
3737
## Runtime flow
3838

3939
1. `main.py` creates the FastAPI app and runtime services.
40-
2. `engine/orchestrator.py` coordinates experiment, session, round, and feedback lifecycle.
41-
3. `engine/generation.py` resolves the active generation backend.
42-
4. `storage/repository.py` persists experiments, sessions, rounds, artifacts, and trace data references.
43-
5. `frontend/` renders the UI, submits async jobs, shows progress, and posts browser events back to the backend.
40+
2. `frontend/` renders the prompt-first setup page, including the editable per-session YAML block.
41+
3. `engine/orchestrator.py` coordinates experiment, session, round, baseline or incumbent carry-forward, and feedback lifecycle.
42+
4. `engine/generation.py` resolves the active generation backend and applies the per-session generation settings.
43+
5. `storage/repository.py` persists experiments, sessions, rounds, artifacts, and trace data references.
44+
6. `frontend/` submits async jobs, shows progress, and posts browser events back to the backend.

app/core/config_yaml.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
# Edit any of these values before creating a new session.
1515
# This YAML is reloaded fresh for each setup page visit or reset action.
1616
#
17-
# sampler: random_local | exploit_orthogonal | uncertainty_guided
17+
# sampler: random_local | exploit_orthogonal | uncertainty_guided | axis_sweep | incumbent_mix
1818
# updater: winner_average | winner_copy | linear_preference
19-
# feedback_mode: scalar_rating | pairwise | top_k
19+
# feedback_mode: scalar_rating | pairwise | top_k | winner_only | approve_reject
20+
# seed_policy: fixed-per-round | fixed-per-candidate | fixed-per-candidate-role
2021
# image_size: WIDTHxHEIGHT, for example 512x512
22+
# guidance_scale: classifier-free guidance strength, for example 7.5
23+
# num_inference_steps: diffusion denoising steps, for example 15 or 30
2124
"""
2225
)
2326

app/core/jobs.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

3-
import asyncio
3+
from concurrent.futures import ThreadPoolExecutor
44
from datetime import datetime
55
from enum import Enum
6-
from threading import Lock, Thread
6+
from threading import Lock
77
from typing import Any, Callable
88
from uuid import uuid4
99

@@ -36,17 +36,20 @@ class JobRecord(BaseModel):
3636
class AsyncJobManager:
3737
"""Small in-memory async job manager for long-running user operations."""
3838

39-
def __init__(self) -> None:
39+
def __init__(self, *, max_workers: int = 4, max_jobs: int = 200) -> None:
4040
self._jobs: dict[str, JobRecord] = {}
4141
self._lock = Lock()
42+
self._executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="stable-steering-job")
43+
self._max_jobs = max_jobs
4244

4345
async def submit(self, operation: str, fn: Callable[[], Any]) -> JobRecord:
4446
"""Create a job record and execute the callable in a worker thread."""
4547

4648
job = JobRecord(operation=operation)
4749
with self._lock:
50+
self._prune_locked()
4851
self._jobs[job.id] = job
49-
Thread(target=self._run_sync, args=(job.id, fn), daemon=True).start()
52+
self._executor.submit(self._run_sync, job.id, fn)
5053
return job.model_copy(deep=True)
5154

5255
async def get(self, job_id: str) -> JobRecord | None:
@@ -90,3 +93,18 @@ def _update(self, job_id: str, **changes: Any) -> None:
9093
for key, value in changes.items():
9194
setattr(job, key, value)
9295
job.updated_at = utc_now()
96+
self._prune_locked()
97+
98+
def _prune_locked(self) -> None:
99+
"""Drop old completed jobs so the in-memory registry remains bounded."""
100+
101+
overflow = len(self._jobs) - self._max_jobs
102+
if overflow <= 0:
103+
return
104+
completed_ids = [
105+
job.id
106+
for job in sorted(self._jobs.values(), key=lambda record: (record.updated_at, record.created_at))
107+
if job.state in {JobState.succeeded, JobState.failed}
108+
]
109+
for job_id in completed_ids[:overflow]:
110+
self._jobs.pop(job_id, None)

app/core/schema.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,38 @@ class SeedPolicy(str, Enum):
5959
fixed_per_candidate_role = "fixed-per-candidate-role"
6060

6161

62+
class SamplerType(str, Enum):
63+
random_local = "random_local"
64+
exploit_orthogonal = "exploit_orthogonal"
65+
uncertainty_guided = "uncertainty_guided"
66+
axis_sweep = "axis_sweep"
67+
incumbent_mix = "incumbent_mix"
68+
69+
70+
class UpdaterType(str, Enum):
71+
winner_average = "winner_average"
72+
winner_copy = "winner_copy"
73+
linear_preference = "linear_preference"
74+
75+
76+
class SteeringMode(str, Enum):
77+
low_dimensional = "low_dimensional"
78+
79+
6280
class StrategyConfig(BaseModel):
6381
"""Experiment-level strategy choices and tunable parameters."""
6482

65-
sampler: str = "random_local"
66-
updater: str = "winner_average"
83+
sampler: SamplerType = SamplerType.random_local
84+
updater: UpdaterType = UpdaterType.winner_average
6785
feedback_mode: FeedbackType = FeedbackType.scalar_rating
6886
seed_policy: SeedPolicy = SeedPolicy.fixed_per_round
69-
steering_mode: str = "low_dimensional"
70-
candidate_count: int = 5
87+
steering_mode: SteeringMode = SteeringMode.low_dimensional
88+
candidate_count: int = Field(default=5, ge=1, le=12)
7189
image_size: str = "512x512"
72-
trust_radius: float = 0.35
73-
anchor_strength: float = 0.15
90+
trust_radius: float = Field(default=0.35, gt=0.0, le=1.0)
91+
anchor_strength: float = Field(default=0.35, ge=0.0, le=2.0)
92+
guidance_scale: float = Field(default=7.5, gt=0.0, le=20.0)
93+
num_inference_steps: int = Field(default=15, ge=1, le=100)
7494
model_name: str = "runwayml/stable-diffusion-v1-5"
7595

7696

0 commit comments

Comments
 (0)