JEV (Just-Enough-View) page compaction
JEV keeps large pages out of the agent's context window. Instead of returning a full page Markdown dump, the agent gets a compact surface index (Tier 1) and fetches only the sections it needs from an in-memory depth store (Tier 2).
Availability
Optional, not experimental:
- Settings โ DECISION MODEL โ JEV PAGE COMPACTION, persisted to
jev.json(IPC-only writes), default off. - Two controls: a master on/off switch (
power) and a read mode (prefer|enforce). The mode is remembered while the switch is off. - Both are disabled until a decision model is configured; the runtime gate is
power && decisionStatus().configured. A legacy{ enabled: true }file migrates topower: true, mode: "prefer"โ never silently to enforce.
Modes
| Mode | Behaviour |
|---|---|
prefer |
Tools lead with the index and descriptions steer reads to it. Full-page reads still work. |
enforce |
Unscoped page reads are refused with JEV_REQUIRED; the agent must use jev_index โ read_section. |
enforce applies to observe(as:"markdown"), observe(as:"text"), and
GET /markdown (the get_page_markdown / harvest_markdown aliases).
Allowed while enforcing:
regionscoped reads (mark id or CSS selector);- bounded reads:
budget > 0or acursor; full: trueโ the logged bypass;- pages at or below 1,500 tokens (the index would not save anything);
- every non-content projection:
som,components,ax,list,table,app_data, plusfind/extract/act/ screenshots.
budget: 0 means "the whole construct", so it is treated as unbounded and
refused.
Refusal contract
{
"success": false,
"code": "JEV_REQUIRED",
"error": "Unscoped page reads are disabled while JEV enforce is on. โฆ",
"page": { "url": "โฆ", "tokens": 14823, "sections": 8 },
"next": [
{ "tool": "jev_index" },
{ "tool": "read_section", "arguments": { "section_id": "sec_1" } }
],
"override": { "parameter": "full", "value": true, "note": "bypasses the funnel and is logged" }
}
HTTP status is 409.
Safety valves
- Cheap precheck: before paying for a harvest, the guard measures
document.body.textContentand refuses an obviously oversized unscoped read in ~15 ms. The post-harvest guard stays authoritative, because inline marks and URLs can push the real markdown over the threshold. - Fail-open: harvest errors, zero sections, or an index that is not smaller than the page serve the read instead of refusing it.
- Loop breaker: after two refusals for the same URL in 60 seconds, the next read is served with a note and counted as a bypass. A naive agent cannot burn its turn budget on the wall.
- Telemetry:
GET /jev/statsreportsblocked,precheck(refusals decided before the harvest),bypassed,loopBreaker,blockedTokensand the active breaker entries.
Tools
| Tool | Route | Purpose |
|---|---|---|
jev_index |
GET /jev/index |
Build/refresh the surface index for the active page. |
read_section |
POST /jev/read |
Fetch one section body (include_children for nested subsections). |
search_page |
POST /jev/search |
Keyword/regex scan over all folded section text, with snippets. |
| โ | GET /jev/stats |
Funnel counters and JEV status. |
Preferred reading path
JEV is not just available when it is active โ the tool surface is arranged so it is the default way to read a page:
tools/listleads withjev_index,read_section,search_page(afterdrive/decidewhen a decision model is configured).jev_indexadvertises itself as the preferred (or, in enforce, mandatory) reading path;observeanddecidecarry a matching note, including theregion/budget/full:truerules in enforce.- The OKF guide (
mark-eleven://skills/guide) swaps its "read a page" recipe tojev_index โ read_sectionwhile JEV is active. navigateresponses end with a one-line JEV nudge, so the reminder arrives at the moment an agent is about to read.GET /tools/configreportsjev: { available, mode }, which is how the MCP server learns the gate and mode at startup.
Activation and mode are read when the MCP server starts, so after flipping the
switch in Settings, reconnect the MCP client (or restart the app) to reorder
tools/list and adopt the enforce text. While JEV is off the three tools are not
registered at all โ their schemas cost ~1k tokens per request, so a disabled
feature must not bill. The bridge side โ the gate, the funnel, the navigate
nudge, and the route errors โ is live either way.
Implementation
src/main/jev/engine.ts: splits harvested markdown at heading boundaries (nested IDssec_1.1), estimates tokens (chars / 4), derives a one-sentence digest per section, and renders theJEV_SURFACE_INDEXwire format.src/main/jev/policy.ts: puredecideFunnelpolicy plus theFunnelBreaker.src/main/primitives/primitives.ts: the single choke point โ markdown and text projections consult the host guard before leaving the process.- The depth store is scoped to the page URL and purged on navigate/back/forward/ reload, tab switch/close, and explicit purge.
- Auto-fold: expanded sections are tracked; past ~6,000 tokens,
read_sectionresponses include fold hints naming the oldest expanded IDs. - Tests:
npm run test:jevandnpm run test:jev:funnel.
Limits
This is token discipline, not a security boundary. read_url,
capture(rendered), screenshots and evaluate_javascript can still pull bytes
without going through the funnel.
JEV covers page reads only. Hypagon REST (wrap_run) is outside it: a
wrapper response is already projected by the response recipe, with maxItems,
maxChars and maxFieldChars, plus the pagination cap.