From 12b1f5536de7504f0af0c1aea38e3f3d3ecc2d4a Mon Sep 17 00:00:00 2001 From: Son Seong Jun <166786347+SonAIengine@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:28:37 +0900 Subject: [PATCH 1/2] feat: advertise bounded 64 KiB headless goal support --- README.md | 6 ++ crates/xgeny-cli/src/composition.rs | 22 ++++++- crates/xgeny-cli/src/main.rs | 1 + .../xgeny-cli/tests/environment_onboarding.rs | 62 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aad1912..27fb4f0 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,12 @@ summary 전에는 model call·plan commit·effect·verification 같은 redacted strict JSON Schema response와 응답의 exact model ID를 지원하는 서버다. 설치·검증·삭제와 OS별 제약은 [시작하기](docs/getting-started.md)와 [모델 온보딩](docs/development/model-onboarding.md)을 따른다. +Headless `run`의 goal은 최대 65,536 UTF-8 바이트입니다. 호스트는 모델 호출 없는 +`run --help`의 `XGENY_MAX_GOAL_BYTES=65536` 표기로 설치된 실행기의 한도를 확인할 수 +있습니다. 이 표기가 없는 이전 실행기는 16,384 바이트로 취급하세요. 초과 입력은 잘라내지 +않고 실행 전에 거절합니다. 모델 context·호출 예산·도구 권한은 확장하지 않으며, 대화형 +REPL의 한 줄/합성 goal 한도는 기존 16 KiB를 유지합니다. + Project, Cargo dependency, Rust standard library, Linux musl과 LLVM libunwind의 배포 고지는 binary에 포함되어 있어 network나 별도 파일 없이 `xgeny licenses`로 확인할 수 있다. diff --git a/crates/xgeny-cli/src/composition.rs b/crates/xgeny-cli/src/composition.rs index 29069c5..a3bb645 100644 --- a/crates/xgeny-cli/src/composition.rs +++ b/crates/xgeny-cli/src/composition.rs @@ -75,7 +75,9 @@ use crate::run_layout::{RunLayout, discover_state_root, generate_run_id}; const WORKSPACE_ID: &str = "primary"; const WORKSPACE_IDENTITY_PROFILE: &str = "xgeny.fs.workspace-root-identity.v1"; const DEFAULT_PLANNER_ID: &str = "xgeny.cli.openai"; -const MAX_GOAL_BYTES: usize = 16 * 1024; +/// Maximum UTF-8 goal size for headless runs. Independent of the planner's +/// bounded context, model-call budget, and capability permissions. +pub const MAX_GOAL_BYTES: usize = 64 * 1024; const MAX_TICKS: u32 = 1_024; const MODEL_CHECK_TIMEOUT: Duration = Duration::from_secs(10); const LOCAL_EXECUTION_PROFILE_DOMAIN: &str = "xgeny.cli.local-execution-profile/v1"; @@ -2658,6 +2660,24 @@ mod tests { use super::*; + #[test] + fn goal_bound_counts_utf8_bytes_without_truncation() { + for unit in ["a", "한", "🧪"] { + let mut goal = unit.repeat(MAX_GOAL_BYTES / unit.len()); + goal.push_str(&"x".repeat(MAX_GOAL_BYTES - goal.len())); + let original = goal.clone(); + assert!(validate_goal(&goal).is_ok()); + assert_eq!(goal, original); + goal.push('x'); + assert!(matches!( + validate_goal(&goal), + Err(PublicRunError::Configuration) + )); + } + assert!(validate_goal("").is_err()); + assert!(validate_goal("valid\0invalid").is_err()); + } + #[test] fn local_route_profile_gate_accepts_new_occurrences_and_legacy_resume_profiles() { assert!(local_profile_matches_effect_kind( diff --git a/crates/xgeny-cli/src/main.rs b/crates/xgeny-cli/src/main.rs index 254c762..13e2fbd 100644 --- a/crates/xgeny-cli/src/main.rs +++ b/crates/xgeny-cli/src/main.rs @@ -187,6 +187,7 @@ struct RunArgs { #[command(flatten)] request_options: RequestOptionArgs, /// Goal sent to the bounded planner. + #[arg(help = format!("Goal sent to the bounded planner. XGENY_MAX_GOAL_BYTES={}", xgeny_cli::MAX_GOAL_BYTES))] goal: String, /// Workspace root opened as the local filesystem capability. #[arg(long, default_value = ".")] diff --git a/crates/xgeny-cli/tests/environment_onboarding.rs b/crates/xgeny-cli/tests/environment_onboarding.rs index e39c12f..47ac248 100644 --- a/crates/xgeny-cli/tests/environment_onboarding.rs +++ b/crates/xgeny-cli/tests/environment_onboarding.rs @@ -457,6 +457,68 @@ fn non_utf8_https_credential_is_rejected_before_any_run_state_is_created() { assert_configuration_before_state(&output, &state); } +#[test] +fn extended_utf8_goal_reaches_provider_intact_with_unchanged_approval_boundary() { + for unit in ["classification evidence ", "회귀 근거 🧪 "] { + let fixture = tempdir().unwrap(); + let workspace = fixture.path().join("workspace"); + let state = fixture.path().join("state"); + fs::create_dir(&workspace).unwrap(); + fs::write(workspace.join("README.md"), "fixture").unwrap(); + let mut goal = unit.repeat(xgeny_cli::MAX_GOAL_BYTES / unit.len()); + goal.push_str(&"x".repeat(xgeny_cli::MAX_GOAL_BYTES - goal.len())); + let server = CompletionServer::spawn(); + let output = xgeny(&state) + .current_dir(&workspace) + .env("XGENY_OPENAI_BASE_URL", &server.base_url) + .env("XGENY_OPENAI_MODEL", MODEL) + .args([ + "run", + "--allow-file", + "README.md", + "--allow-remote-model-egress", + &goal, + ]) + .output() + .unwrap(); + assert_read_approval_pause(&output); + let request = server.handle.join().unwrap(); + let body = request_body(&request); + assert!(body["messages"].as_array().unwrap().iter().any(|message| { + message["content"] + .as_str() + .is_some_and(|content| content.contains(&goal)) + })); + } +} + +#[test] +fn advertised_goal_bound_matches_validation_and_oversize_creates_no_state() { + let fixture = tempdir().unwrap(); + let state = fixture.path().join("state"); + let help = xgeny(&state).args(["run", "--help"]).output().unwrap(); + assert!(help.status.success()); + assert!(String::from_utf8(help.stdout).unwrap().contains(&format!( + "XGENY_MAX_GOAL_BYTES={}", + xgeny_cli::MAX_GOAL_BYTES + ))); + let goal = "x".repeat(xgeny_cli::MAX_GOAL_BYTES + 1); + let output = xgeny(&state) + .current_dir(fixture.path()) + .env("XGENY_OPENAI_BASE_URL", "http://127.0.0.1:1/v1") + .env("XGENY_OPENAI_MODEL", MODEL) + .args([ + "run", + "--allow-dir", + ".", + "--allow-remote-model-egress", + &goal, + ]) + .output() + .unwrap(); + assert_configuration_before_state(&output, &state); +} + fn xgeny(state: &Path) -> Command { let mut command = Command::new(env!("CARGO_BIN_EXE_xgeny")); command From ae7ffe8822a65cbeb284156d66dd3ce93214dd98 Mon Sep 17 00:00:00 2001 From: Son Seong Jun <166786347+SonAIengine@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:29:13 +0900 Subject: [PATCH 2/2] fix: preserve Windows argv-safe goal bound --- README.md | 3 ++- crates/xgeny-cli/src/composition.rs | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27fb4f0..899e572 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,8 @@ summary 전에는 model call·plan commit·effect·verification 같은 redacted strict JSON Schema response와 응답의 exact model ID를 지원하는 서버다. 설치·검증·삭제와 OS별 제약은 [시작하기](docs/getting-started.md)와 [모델 온보딩](docs/development/model-onboarding.md)을 따른다. -Headless `run`의 goal은 최대 65,536 UTF-8 바이트입니다. 호스트는 모델 호출 없는 +Unix headless `run`의 goal은 최대 65,536 UTF-8 바이트입니다. Windows는 OS command-line +한도 때문에 기존 16,384 바이트를 유지하며 도움말에도 실제 한도를 표시합니다. 호스트는 모델 호출 없는 `run --help`의 `XGENY_MAX_GOAL_BYTES=65536` 표기로 설치된 실행기의 한도를 확인할 수 있습니다. 이 표기가 없는 이전 실행기는 16,384 바이트로 취급하세요. 초과 입력은 잘라내지 않고 실행 전에 거절합니다. 모델 context·호출 예산·도구 권한은 확장하지 않으며, 대화형 diff --git a/crates/xgeny-cli/src/composition.rs b/crates/xgeny-cli/src/composition.rs index a3bb645..f6884ce 100644 --- a/crates/xgeny-cli/src/composition.rs +++ b/crates/xgeny-cli/src/composition.rs @@ -77,7 +77,11 @@ const WORKSPACE_IDENTITY_PROFILE: &str = "xgeny.fs.workspace-root-identity.v1"; const DEFAULT_PLANNER_ID: &str = "xgeny.cli.openai"; /// Maximum UTF-8 goal size for headless runs. Independent of the planner's /// bounded context, model-call budget, and capability permissions. +#[cfg(not(windows))] pub const MAX_GOAL_BYTES: usize = 64 * 1024; +/// Windows command lines have a lower OS bound; retain the legacy allowance. +#[cfg(windows)] +pub const MAX_GOAL_BYTES: usize = 16 * 1024; const MAX_TICKS: u32 = 1_024; const MODEL_CHECK_TIMEOUT: Duration = Duration::from_secs(10); const LOCAL_EXECUTION_PROFILE_DOMAIN: &str = "xgeny.cli.local-execution-profile/v1";