Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
run: |
mkdir -p _site
cp site/index.html site/zh.html site/styles.css site/app.js site/language.js site/robots.txt site/sitemap.xml _site/
cp site/agent-harness.html site/agent-harness.css site/agent-harness.js _site/
cp -r site/assets site/docs site/files _site/
- uses: actions/upload-pages-artifact@v4
if: github.event_name != 'pull_request'
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ is overhead.
</tr>
</table>

Watch all three in the [nine-second overview](https://simplejev.github.io/JevAny/assets/media/docs/jev-agent-harness.mp4),
or open the [agent harness demos](https://simplejev.github.io/JevAny/agent-harness.html).

Broader paired evaluations show that gains vary by task. The [full results](https://simplejev.github.io/JevAny/docs/reports-JevAny_Tech_Report_Agent_Harness_Appendix.html),
[delegation protocol](https://simplejev.github.io/JevAny/docs/experiments-AGENT_HARNESS_FRONTIER_PROTOCOL.html), and
[technical report](https://simplejev.github.io/JevAny/files/reports/JevAny_Tech_Report.pdf) describe
Expand Down
3 changes: 3 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
</tr>
</table>

三个案例也可在[九秒总览](https://simplejev.github.io/JevAny/assets/media/docs/jev-agent-harness.mp4)中连续观看,或打开
[Agent harness 演示页](https://simplejev.github.io/JevAny/agent-harness.html)查看单独回放。

下表展示更多配对评测结果,收益随任务而变化。[完整结果](https://simplejev.github.io/JevAny/docs/reports-JevAny_Tech_Report_Agent_Harness_Appendix.html)、
[委托协议](https://simplejev.github.io/JevAny/docs/experiments-AGENT_HARNESS_FRONTIER_PROTOCOL.html)和
[技术报告](https://simplejev.github.io/JevAny/files/reports/JevAny_Tech_Report.pdf)说明了 Jev 适合处理哪些选择,以及何时交回 LLM。
Expand Down
Binary file modified docs/demos/jev-agent-harness.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/demos/jev-decision-frozen-lake-v2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/demos/jev-decision-terminal-v2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/demos/jev-decision-webshop-v2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
321 changes: 269 additions & 52 deletions scripts/render_agent_decision_demos_v2.py

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions scripts/render_agent_harness_demo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Render recorded Terminal-Bench, WebShop, and FrozenLake harness traces."""
"""Shared theme and legacy helpers; the CLI exports the current paired demos."""

from __future__ import annotations

Expand Down Expand Up @@ -444,7 +444,7 @@ def summary_frame() -> Image.Image:
return image


def main() -> None:
def legacy_main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--out", default="docs/demos/jev-agent-harness.gif")
parser.add_argument("--sqlite-out", default="docs/demos/jev-agent-harness-sqlite.gif")
Expand Down Expand Up @@ -563,5 +563,11 @@ def save(output_name: str, frames: list[Image.Image], durations: list[int]) -> N
)


def main() -> None:
from render_agent_decision_demos_v2 import main as render_current_demos

render_current_demos()


if __name__ == "__main__":
main()
127 changes: 127 additions & 0 deletions scripts/render_agent_robot_icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""Small, deterministic robot portraits for the recorded agent demos."""

from __future__ import annotations

from functools import lru_cache

from PIL import Image, ImageColor, ImageDraw

from render_agent_harness_demo import ACCENT, AMBER, BLUE


PORTRAIT_STYLES = {
"LLM": ("#c3dcff", "#4674a9", "#10233f"),
"Jev": ("#a9f0df", "#237768", "#092d2b"),
"idle": ("#43516a", "#334158", "#121b2c"),
}
IDLE_EDGE, EYE_LIGHT, IDLE_LIGHT = "#52617a", "#f1fbff", "#8290a6"
ROBOT_COLORS = tuple(color for style in PORTRAIT_STYLES.values() for color in style) + (
IDLE_EDGE, EYE_LIGHT, IDLE_LIGHT,
)


@lru_cache(maxsize=128)
def actor_icon(actor: str, size: int, active: bool = True, phase: int = 0) -> Image.Image:
"""Return an antialiased portrait; motion is decorative, not measured time."""
image = Image.new("RGBA", (size * 3, size * 3))
draw = ImageDraw.Draw(image)
scale = size * 3 / 100
accent = BLUE if actor == "LLM" else ACCENT if actor == "Jev" else AMBER
edge = accent if active else IDLE_EDGE
shell, joint, face = PORTRAIT_STYLES[
"idle" if not active else "LLM" if actor == "LLM" else "Jev"
]
light = EYE_LIGHT if active else IDLE_LIGHT
phase = phase % 6 if active else 0

def coords(values: tuple | list) -> tuple:
return tuple(round(value * scale) for value in values)

def rgba(color: str, alpha: int) -> tuple[int, int, int, int]:
return (*ImageColor.getrgb(color), alpha)

def line(points: tuple | list, fill: str | tuple, width: float = 2) -> None:
draw.line(coords(points), fill=fill, width=max(1, round(width * scale)),
joint="curve")

def rect(box: tuple, radius: float, fill: str | tuple,
outline: str | tuple | None = None, width: float = 1.5) -> None:
draw.rounded_rectangle(coords(box), round(radius * scale), fill=fill,
outline=outline, width=max(1, round(width * scale)))

def ellipse(box: tuple, fill: str | tuple, outline: str | tuple | None = None,
width: float = 1) -> None:
draw.ellipse(coords(box), fill=fill, outline=outline,
width=max(1, round(width * scale)))

def polygon(points: list[tuple], fill: str | tuple) -> None:
draw.polygon([coords(point) for point in points], fill=fill)

if active:
ellipse((7, 13, 95, 98), rgba(accent, 13))
ellipse((15, 22, 89, 94), rgba(accent, 14))
ellipse((24, 88, 80, 95), rgba(accent if active else edge, 35))

if actor == "LLM":
# Broad shell, stable stance, and three small planning lights.
rect((30, 68, 73, 85), 8, joint, edge)
rect((32, 82, 45, 90), 4, shell, edge)
rect((59, 82, 72, 90), 4, shell, edge)
rect((20, 69, 30, 80), 4, joint, edge)
rect((74, 69, 84, 80), 4, joint, edge)
rect((11, 42, 21, 59), 4, joint, edge)
rect((82, 42, 92, 59), 4, joint, edge)
line((51, 18, 51, 28), edge, 3)
ellipse((47, 11, 55, 19), shell, edge)
rect((19, 26, 84, 73), 15, shell, edge, 2)
rect((26, 36, 77, 65), 10, face)
line((36, 31, 65, 31), rgba(light, 190), 2)
rect((36, 44, 42, 55), 3, edge)
rect((61, 44, 67, 55), 3, edge)
ellipse((37, 45, 40, 48), light)
ellipse((62, 45, 65, 48), light)
rect((46, 59, 57, 61), 1, rgba(edge, 150))
for index in range(3):
left = 42 + index * 8
ellipse((left, 76, left + 4, 80), edge if active else joint)
if active:
line((66, 20, 75, 11, 87, 17), rgba(accent, 100), 1)
for index, (cx, cy) in enumerate(((66, 20), (75, 11), (87, 17))):
focused = index == phase // 2
radius = 3 if focused else 2
ellipse((cx - radius, cy - radius, cx + radius, cy + radius),
light if focused else accent)
elif actor == "Jev":
# Compact shell, forward stance, and short moving trails.
if active:
for index, y in enumerate((43, 55, 67)):
shift = (phase * 3 + index * 4) % 10
line((3 + shift, y, 23 + shift // 2, y),
rgba(accent, 115 + index * 45), 2)
rect((42, 66, 77, 80), 7, joint, edge)
polygon([(43, 77), (53, 79), (44, 88), (31, 88)], shell)
polygon([(66, 78), (75, 75), (87, 86), (77, 89)], shell)
line((34, 89, 44, 89), edge, 2)
line((77, 89, 87, 86), edge, 2)
rect((27, 59, 39, 68), 4, joint, edge)
rect((82, 56, 92, 64), 4, joint, edge)
rect((32, 31, 87, 69), 12, shell, edge, 2)
line((45, 35, 74, 32), rgba(light, 190), 2)
rect((40, 42, 83, 62), 8, face)
rect((48, 47, 54, 55), 3, edge)
rect((68, 46, 74, 54), 3, edge)
ellipse((49, 47, 52, 49), light)
ellipse((69, 46, 72, 48), light)
polygon([(65, 12), (54, 29), (63, 28), (59, 39),
(77, 20), (67, 21), (73, 12)], edge)
polygon([(60, 69), (54, 76), (59, 76), (56, 81),
(66, 73), (61, 73)], light)
else:
# Environment feedback and verification share the amber result symbol.
polygon([(50, 17), (82, 29), (78, 60), (67, 78), (50, 90),
(31, 78), (21, 60), (18, 29)], rgba(accent, 40))
line((50, 17, 82, 29, 78, 60, 67, 78, 50, 90,
31, 78, 21, 60, 18, 29, 50, 17), edge, 2)
line((33, 51, 46, 64, 69, 38), light, 6)

return image.resize((size, size), Image.Resampling.LANCZOS)
54 changes: 54 additions & 0 deletions site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,60 @@ released checkpoints and the supported-model catalog. Guides, the technical
report and linked reference files are hosted on the site. GitHub remains an
explicit source-code link; checkpoint download links point to Hugging Face.

The README and `agent-harness.html` use the same paired comparisons from
`scripts/render_agent_decision_demos_v2.py`. Three individual GIFs show WebShop,
FrozenLake, and SQLite recovery. `docs/demos/jev-agent-harness.gif` joins those
replays in that order into a nine-second overview, preserving every frame and
its timing. The gallery uses matching MP4/WebM videos and WebP posters.

Both lanes retain the measured completion-time ratio within each three-second loop.
Recorded LLM call totals are prominent alongside each lane's clock. Task
constraints stay visible; large actor panels identify the current phase,
and recorded menus highlight the selected option. The footer shows LLM tokens
saved, wall time saved, and the task result for this recorded pair. Large
highlighted percentages lead the two savings cards, with absolute savings and
original totals alongside them. Percentages use the LLM-only baseline. These
are full-run measurements for one recorded pair.
A fixed role bar shows each controller once and highlights only
the current controller. When control returns to the LLM, the same LLM position
lights up again. Both roles dim when the task completes, and completed lanes
remain frozen.

Shared robot portraits in `scripts/render_agent_robot_icons.py` distinguish the
blue LLM from the smaller teal Jev. Planning lights and short motion trails
animate only on the active controller; inactive portraits stay gray and still.
These decorative animations do not represent measured inference time. Amber
result symbols mark completion. Icons are drawn and antialiased locally with
Pillow, so all three cases use the same artwork without external image assets.

`agent-harness.html` leads with the overview, followed by the three individual
replays and a short explanation of each delegation: choosing within LLM-provided
menus, retaining one plan across local moves, and choosing useful evidence.
It uses the same navy, blue, teal, and amber palette as the GIFs.
Playback controls provide pause, seek, and fullscreen beneath the frame,
keeping the savings visible even when paused. Native controls remain available
when JavaScript is disabled. Only the overview
starts automatically; videos pause offscreen and when the tab is hidden, and
only one plays at a time. Reduced-motion preferences disable automatic playback.
A manual pause or selection takes precedence over automatic playback.

The gallery links to the recorded traces, delegation protocol, and full results.
WebShop uses seed 3107 and Jev 4B from the D4 supplement; SQLite and FrozenLake
keep their recorded cases. Savings describe one recorded pair, with the broader
D2 matrix, separate D4 supplement, fallback cases, and uncertainty documented
in the report. Rendering uses existing recordings and runs no model or environment.

Regenerate all four GIFs and their site media from the repository root:

```bash
python3 -m pip install pillow imageio-ffmpeg
python3 scripts/render_agent_decision_demos_v2.py --site-media
```

Omit `--site-media` to export only GIFs. Video conversion uses ffmpeg;
imageio-ffmpeg is optional when ffmpeg is already on PATH. The legacy command
`python3 scripts/render_agent_harness_demo.py` also exports this presentation.

Regenerate the homepage's data sections and documentation from the repository:

```bash
Expand Down
89 changes: 89 additions & 0 deletions site/agent-harness.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@font-face {
font-family: "DM Sans";
src: url("assets/fonts/dm-sans-latin.woff2") format("woff2");
font-weight: 100 1000;
font-display: swap;
}

:root {
color-scheme: dark;
--background: #070b14;
--panel: #11182a;
--border: #35415d;
--text: #f7f8ff;
--muted: #9ca8c2;
--llm: #60a5fa;
--jev: #2dd4bf;
--result: #f59e0b;
}

* { box-sizing: border-box; }
html { scroll-padding-top: 24px; }
body {
margin: 0;
background: var(--background);
color: var(--text);
font-family: "DM Sans", sans-serif;
line-height: 1.6;
}
a { color: var(--llm); text-underline-offset: 4px; }
a:focus-visible { outline: 2px solid var(--llm); outline-offset: 5px; }
.wrap { width: min(100% - 64px, 1328px); margin-inline: auto; }
.skip-link { position: absolute; top: -80px; left: 24px; padding: 10px 16px; background: var(--panel); z-index: 2; }
.skip-link:focus { top: 12px; }
.page-header, .page-footer { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.page-header { padding-block: 24px; border-bottom: 1px solid var(--border); }
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--text); font-size: 21px; font-weight: 750; text-decoration: none; }
nav, .evidence-links { display: flex; gap: 24px; flex-wrap: wrap; }
nav { font-size: 14px; }
.intro { padding-block: 48px 36px; }
.eyebrow { margin: 0 0 10px; color: var(--jev); font-size: 12px; font-weight: 650; letter-spacing: 1.3px; }
h1, h2, h3 { line-height: 1.1; letter-spacing: -.035em; }
h1 { margin: 10px 0 20px; font-size: clamp(36px, 5vw, 62px); font-weight: 650; }
h2 { margin: 0; font-size: clamp(26px, 3vw, 36px); font-weight: 600; }
h3 { margin: 12px 0; font-size: 28px; font-weight: 600; }
.lead { max-width: 720px; color: var(--muted); font-size: 19px; }
.role-key { display: flex; flex-wrap: wrap; gap: 24px; margin-top: 24px; color: var(--muted); font-size: 14px; }
.role-key span { display: inline-flex; align-items: center; gap: 8px; }
.role-key i { display: inline-block; width: 8px; height: 8px; border-radius: 50%; }
.role-key .llm { background: var(--llm); }
.role-key .jev { background: var(--jev); }
.role-key .result { background: var(--result); }
.section-heading { display: flex; align-items: end; justify-content: space-between; flex-wrap: wrap; gap: 20px; margin-bottom: 24px; }
.section-heading > a { font-size: 14px; }
.overview { padding-block: 24px 48px; border-bottom: 1px solid var(--border); }
figure { max-width: 1080px; margin: 0; }
.harness-video { display: block; width: 100%; height: auto; aspect-ratio: 1080 / 700; border-radius: 10px; background: var(--background); }
.player { min-width: 0; }
.playback-controls { display: grid; grid-template-columns: 66px minmax(0, 1fr) auto auto; align-items: center; gap: 12px; margin-top: 12px; }
.playback-controls button { min-height: 36px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--text); font: inherit; font-size: 13px; cursor: pointer; }
.playback-controls button:hover { border-color: var(--llm); }
.playback-controls button:focus-visible, .playback-controls input:focus-visible { outline: 2px solid var(--llm); outline-offset: 4px; }
.playback-controls input { width: 100%; margin: 0; accent-color: var(--jev); cursor: pointer; }
.playback-time { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.player:fullscreen { display: flex; flex-direction: column; justify-content: center; padding: 24px; background: var(--background); }
.player:fullscreen .harness-video { max-height: calc(100dvh - 108px); object-fit: contain; }
figcaption { margin-top: 16px; color: var(--muted); font-size: 14px; }
.cases { padding-top: 48px; }
.case { display: grid; grid-template-columns: minmax(0, 2.4fr) minmax(230px, 1fr); align-items: center; gap: 36px; padding-block: 36px; border-bottom: 1px solid var(--border); }
.case-copy p { color: var(--muted); font-size: 16px; }
.case-copy .eyebrow { color: var(--jev); font-size: 12px; }
.case-copy .scope { margin-block: 20px; font-size: 13px; }
.case-copy a { font-size: 14px; }
.evidence { max-width: 860px; padding-block: 52px; }
.evidence p:not(.eyebrow) { color: var(--muted); }
.evidence-links { margin-top: 24px; font-size: 14px; }
.page-footer { padding-block: 24px; border-top: 1px solid var(--border); color: var(--muted); font-size: 13px; }

@media (max-width: 800px) {
.wrap { width: calc(100% - 32px); }
.page-header { align-items: start; }
nav { justify-content: end; gap: 8px 16px; max-width: 220px; font-size: 13px; }
.intro { padding-block: 32px 24px; }
.lead { font-size: 17px; }
.case { grid-template-columns: minmax(0, 1fr); gap: 16px; padding-block: 28px; }
.case-copy .scope { margin-block: 14px; }
.overview { padding-bottom: 32px; }
.cases { padding-top: 32px; }
.evidence { padding-block: 36px; }
}
Loading
Loading