Skip to content

Commit 2cd2631

Browse files
committed
feat: add Graphify context compression
AI-assisted: Jcode
1 parent dd59242 commit 2cd2631

12 files changed

Lines changed: 967 additions & 36 deletions

File tree

crates/jcode-app-core/src/agent/tools.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,28 @@ use crate::tool::ToolOutput;
44

55
pub(super) const MAX_TOOL_OUTPUT_CHARS_FOR_HISTORY: usize = 512 * 1024;
66

7+
fn graph_compact_tool_limit() -> Option<usize> {
8+
let config = crate::config::config();
9+
(config.context_compression.mode == crate::config::ContextCompressionMode::GraphCompact)
10+
.then_some(config.context_compression.max_tool_output_chars)
11+
}
12+
13+
fn compact_tool_output_if_enabled(tool_name: &str, content: &str) -> Option<String> {
14+
let limit = graph_compact_tool_limit()?;
15+
if content.chars().count() <= limit {
16+
return None;
17+
}
18+
let store_root = crate::storage::jcode_dir().ok()?.join("context-store");
19+
crate::context_compiler::store_and_compact_tool_output(tool_name, content, limit, &store_root)
20+
.ok()
21+
.map(|stored| stored.compact_content)
22+
}
23+
724
pub(super) fn cap_tool_output_for_history(tool_name: &str, mut output: ToolOutput) -> ToolOutput {
25+
if let Some(compact) = compact_tool_output_if_enabled(tool_name, &output.output) {
26+
output.output = compact;
27+
return output;
28+
}
829
if output.output.chars().count() <= MAX_TOOL_OUTPUT_CHARS_FOR_HISTORY {
930
return output;
1031
}
@@ -19,6 +40,9 @@ pub(super) fn cap_tool_output_for_history(tool_name: &str, mut output: ToolOutpu
1940
}
2041

2142
pub(super) fn cap_sdk_tool_content_for_history(tool_name: &str, content: String) -> String {
43+
if let Some(compact) = compact_tool_output_if_enabled(tool_name, &content) {
44+
return compact;
45+
}
2246
if content.chars().count() <= MAX_TOOL_OUTPUT_CHARS_FOR_HISTORY {
2347
return content;
2448
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use jcode_base::context_compiler::{compile_graphify_context, estimate_tokens};
2+
use serde::Serialize;
3+
use std::process::Command;
4+
5+
#[derive(Serialize)]
6+
struct EvaluationReport {
7+
task: String,
8+
baseline: Metrics,
9+
graph_compact: Metrics,
10+
input_compression_ratio: f64,
11+
root_nodes_preserved: usize,
12+
}
13+
14+
#[derive(Serialize)]
15+
struct Metrics {
16+
context_bytes: usize,
17+
context_tokens_estimated: usize,
18+
context_items: usize,
19+
compression_mode: &'static str,
20+
compression_version: &'static str,
21+
}
22+
23+
fn main() {
24+
let task = std::env::args().skip(1).collect::<Vec<_>>().join(" ");
25+
let task = if task.trim().is_empty() {
26+
"messages_for_provider context compaction".to_string()
27+
} else {
28+
task
29+
};
30+
let budget = std::env::var("JCODE_CONTEXT_GRAPH_TOKEN_BUDGET")
31+
.ok()
32+
.and_then(|value| value.parse().ok())
33+
.unwrap_or(1_200);
34+
let max_items = std::env::var("JCODE_CONTEXT_MAX_GRAPH_ITEMS")
35+
.ok()
36+
.and_then(|value| value.parse().ok())
37+
.unwrap_or(24);
38+
39+
let output = Command::new("graphify")
40+
.args(["query", &task, "--format", "compact"])
41+
.output()
42+
.expect("graphify must be installed and available on PATH");
43+
if !output.status.success() {
44+
eprintln!(
45+
"graphify query failed: {}",
46+
String::from_utf8_lossy(&output.stderr)
47+
);
48+
std::process::exit(2);
49+
}
50+
51+
let raw = String::from_utf8_lossy(&output.stdout);
52+
let candidate_items = raw.lines().filter(|line| line.starts_with("NODE ")).count();
53+
let compiled = compile_graphify_context(&task, &raw, budget, max_items);
54+
let compiled_json = compiled.to_prompt_json();
55+
let baseline_tokens = estimate_tokens(&raw);
56+
let compressed_tokens = estimate_tokens(&compiled_json);
57+
let report = EvaluationReport {
58+
task,
59+
baseline: Metrics {
60+
context_bytes: raw.len(),
61+
context_tokens_estimated: baseline_tokens,
62+
context_items: candidate_items,
63+
compression_mode: "off",
64+
compression_version: "baseline",
65+
},
66+
graph_compact: Metrics {
67+
context_bytes: compiled_json.len(),
68+
context_tokens_estimated: compressed_tokens,
69+
context_items: compiled.items.len(),
70+
compression_mode: compiled.mode,
71+
compression_version: compiled.version,
72+
},
73+
input_compression_ratio: if compressed_tokens == 0 {
74+
0.0
75+
} else {
76+
baseline_tokens as f64 / compressed_tokens as f64
77+
},
78+
root_nodes_preserved: compiled.root_nodes.len(),
79+
};
80+
81+
println!("{}", serde_json::to_string_pretty(&report).unwrap());
82+
}

crates/jcode-base/src/config.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
66
pub use jcode_config_types::{
77
AgentsConfig, AmbientConfig, AuthConfig, AutoJudgeConfig, AutoReviewConfig, CompactionConfig,
8-
CompactionMode, CrossProviderFailoverMode, DiagramDisplayMode, DiagramPanePosition,
9-
DiffDisplayMode, DisplayConfig, FeatureConfig, GatewayConfig, HookCommands, HooksConfig,
10-
KeybindingsConfig, LatexRenderingMode, LaunchHotkeyEntry, LaunchHotkeysConfig,
11-
MarkdownSpacingMode, NamedProviderAuth, NamedProviderConfig, NamedProviderModelConfig,
12-
NamedProviderType, NativeScrollbarConfig, NotificationsConfig, OverscrollStatusMode,
13-
PowerConfig, ProviderConfig, ReasoningDisplayMode, SafetyConfig, SessionPickerResumeAction,
14-
SponsorsConfig, SwarmSpawnMode, SwarmStripLayout, TerminalConfig, UpdateChannel,
15-
WebSearchConfig, WebSearchEngine,
8+
CompactionMode, ContextCompressionConfig, ContextCompressionMode, CrossProviderFailoverMode,
9+
DiagramDisplayMode, DiagramPanePosition, DiffDisplayMode, DisplayConfig, FeatureConfig,
10+
GatewayConfig, HookCommands, HooksConfig, KeybindingsConfig, LatexRenderingMode,
11+
LaunchHotkeyEntry, LaunchHotkeysConfig, MarkdownSpacingMode, NamedProviderAuth,
12+
NamedProviderConfig, NamedProviderModelConfig, NamedProviderType, NativeScrollbarConfig,
13+
NotificationsConfig, OverscrollStatusMode, PowerConfig, ProviderConfig, ReasoningDisplayMode,
14+
SafetyConfig, SessionPickerResumeAction, SponsorsConfig, SwarmSpawnMode, SwarmStripLayout,
15+
TerminalConfig, UpdateChannel, WebSearchConfig, WebSearchEngine,
1616
};
1717
use serde::{Deserialize, Serialize};
1818
use std::collections::{BTreeMap, BTreeSet, HashSet};
@@ -116,6 +116,10 @@ const CONFIG_ENV_KEYS: &[&str] = &[
116116
"JCODE_MEMORY_MODEL",
117117
"JCODE_MEMORY_SIDECAR_ENABLED",
118118
"JCODE_MEMORY_GRAPHIFY_ENABLED",
119+
"JCODE_CONTEXT_COMPRESSION_MODE",
120+
"JCODE_CONTEXT_GRAPH_TOKEN_BUDGET",
121+
"JCODE_CONTEXT_MAX_GRAPH_ITEMS",
122+
"JCODE_CONTEXT_MAX_TOOL_OUTPUT_CHARS",
119123
"JCODE_MEMORY_VAULT_ENABLED",
120124
"JCODE_MEMORY_PGVECTOR_ENABLED",
121125
"JCODE_MEMORY_VAULT_ROOT",
@@ -525,6 +529,9 @@ pub struct Config {
525529
/// Compaction configuration
526530
pub compaction: CompactionConfig,
527531

532+
/// Graph-aware request context compilation.
533+
pub context_compression: ContextCompressionConfig,
534+
528535
/// Power-management configuration (prevent sleep while streaming)
529536
pub power: PowerConfig,
530537

crates/jcode-base/src/config/default_file.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,22 @@ memory_vault_enabled = false
470470
# Pgvector RAG search via /sharedssd/scripts/search_memory.py:
471471
memory_pgvector_enabled = false
472472
473+
[context_compression]
474+
# Graph-aware context compilation. "off" preserves the current strategy and is
475+
# the reproducible baseline. "graph_compact" queries Graphify and injects a
476+
# bounded, revision-addressed JSON context package. Env:
477+
# JCODE_CONTEXT_COMPRESSION_MODE
478+
mode = "off"
479+
# Maximum estimated Graphify tokens injected per compiled package.
480+
# Env: JCODE_CONTEXT_GRAPH_TOKEN_BUDGET
481+
graph_token_budget = 1200
482+
# Maximum graph nodes emitted per package. Env: JCODE_CONTEXT_MAX_GRAPH_ITEMS
483+
max_graph_items = 24
484+
# Tool output characters retained in prompt history in graph-compact mode. Full
485+
# oversized output is stored under JCODE_HOME/context-store and referenced from
486+
# the compact result. Env: JCODE_CONTEXT_MAX_TOOL_OUTPUT_CHARS
487+
max_tool_output_chars = 65536
488+
473489
[terminal]
474490
# Without a hook, clients inside tmux automatically use a right-side pane.
475491
# Set JCODE_TERMINAL to force a supported terminal emulator instead.

crates/jcode-base/src/config/display_summary.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ impl Config {
100100
- Memory: {}
101101
- Memory sidecar: {}
102102
- Memory enrichment: graphify={}, vault={}, pgvector={}
103+
- Context compression: {} (graph budget={} tokens, max items={}, tool chars={})
103104
- Ambient: {}
104105
105106
**Gateway:**
@@ -293,6 +294,10 @@ impl Config {
293294
self.agents.memory_graphify_enabled,
294295
self.agents.memory_vault_enabled,
295296
self.agents.memory_pgvector_enabled,
297+
self.context_compression.mode.as_str(),
298+
self.context_compression.graph_token_budget,
299+
self.context_compression.max_graph_items,
300+
self.context_compression.max_tool_output_chars,
296301
self.ambient
297302
.model
298303
.as_deref()

crates/jcode-base/src/config/env_overrides.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,26 @@ impl Config {
414414
self.agents.memory_graphify_enabled = parsed;
415415
}
416416
}
417+
if let Ok(v) = std::env::var("JCODE_CONTEXT_COMPRESSION_MODE") {
418+
if let Some(parsed) = ContextCompressionMode::parse(&v) {
419+
self.context_compression.mode = parsed;
420+
}
421+
}
422+
if let Ok(v) = std::env::var("JCODE_CONTEXT_GRAPH_TOKEN_BUDGET") {
423+
if let Ok(parsed) = v.trim().parse::<usize>() {
424+
self.context_compression.graph_token_budget = parsed.max(1);
425+
}
426+
}
427+
if let Ok(v) = std::env::var("JCODE_CONTEXT_MAX_GRAPH_ITEMS") {
428+
if let Ok(parsed) = v.trim().parse::<usize>() {
429+
self.context_compression.max_graph_items = parsed.max(1);
430+
}
431+
}
432+
if let Ok(v) = std::env::var("JCODE_CONTEXT_MAX_TOOL_OUTPUT_CHARS") {
433+
if let Ok(parsed) = v.trim().parse::<usize>() {
434+
self.context_compression.max_tool_output_chars = parsed.max(1);
435+
}
436+
}
417437
if let Ok(v) = std::env::var("JCODE_MEMORY_VAULT_ENABLED") {
418438
if let Some(parsed) = parse_env_bool(&v) {
419439
self.agents.memory_vault_enabled = parsed;

0 commit comments

Comments
 (0)