Skip to content
Merged
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
4 changes: 4 additions & 0 deletions MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,7 @@

- HTTP 확장1은 [PR #139](https://github.com/PlateerLab/xgen-cli/pull/139)로 main `0c29e15`에 머지했다. [Fast/Linux PR 검사](https://github.com/PlateerLab/xgen-cli/actions/runs/41084874587) 통과.
- HTTP 확장2(ADR-0082, 구현·로컬 검증 완료): query 배열은 OpenAPI3 form/explode=true와 Swagger2 multi만 지원한다. scalar items와 배열 제약을 전체 schema로 검증하고 반복 query pair를 host에서 인코딩한다. 다른 직렬화·object items는 선택 제외/required 거절을 유지한다. 실행 profile은 한 상수 `xgen.http.read/v2`로 binding/profile에 고정한다. 이전 바이너리의 미완료 HTTP Run은 새 바이너리 resume에서 execution_profile drift로 요청0회 거절, 완료 Run은 네트워크 없이 재생됨을 실제 loopback fixture로 확인했다. 배열 wire·잘못된 값 0회 요청도 shipment/sensor에서 확인했으며 Rust781개(4 ignored)·pinned backend/컴파일 CLI 전체144개 검사(7 skipped)·Clippy·fmt·문서·workflow 검사를 통과했다.

- HTTP 확장2는 [PR #140](https://github.com/PlateerLab/xgen-cli/pull/140)로 main `4fbbcc9`에 머지했고 Fast/Linux 검사가 통과했다.
- HTTP 확장3(ADR-0083, 구현·로컬 검증 완료): host operationId 목록으로 GET/POST 조회를 지정하고 POST JSON 본문을 원본 schema로 검증한다. Capability2.0.0/profilev3로 고정하며 목록 변경은 기존 http_connections drift로 거절한다. shipment/measurement loopback POST·잘못된 본문0회 요청·미지정POST0회·drift를 확인했다. 실제 외부 서비스 통합과 모델 품질은 아직 미검증이다.
- HTTP 확장3 검증: Rust782개(4 ignored), Python148개(7 skipped), Clippy·fmt·protocol·문서·workflow 검사 통과. 전체 Rust 검사 도중 별도 빌드가 xgen 실행 파일을 바꿔 기존 executable drift 검사1건이 거절됐고, 빌드와 분리해 다시 돌린 전체 검사는 통과했다. 독립 diff 검토의 가설은 원본 계약·승인 경계·실행 전 body 검증과 대조했으며 추가 재현된 결함은 없었다.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Local-first general-purpose agent CLI and harness.

> 현재 상태: 프로토콜 v0.1 검증, dependency DAG와 Receipt-gated 재개 frontier를 갖춘 model-free WorkGraph, embedded SQLite schema 8 store, effect 실행·복구 조정기, 결정론적 Capability Registry·Router, I/O 없는 Permission Broker, secret-free invocation material 복구, Direct Executor와 Core-owned Execution Receipt 검증 경계, 장기 Run용 VerifiedRunIndex를 실행할 수 있습니다. Provider-neutral bounded AgentLoop는 비신뢰 `PlanProposal`을 검증해 다중 Step DAG와 reconstructable input sidecar를 원자 commit하고 재시작 뒤 한 frontier action씩 이어갑니다. Model call은 provider 호출 전에 보수적인 possible-send slot을 journal에 예약하며 accepted/rejected/Unknown을 재시작 뒤 복원하고, 불확정 호출을 자동 재시도하지 않습니다. 별도 `xgen-provider-openai` leaf adapter는 OpenAI-compatible Chat Completions의 단일 요청과 strict structured proposal을 이 lifecycle에 연결합니다. 내부 WorkGraph는 planned `ReadOnly`/`Idempotent`/`NonIdempotent`, Core Receipt v2의 bounded Artifact commitment와 event-anchored typed `ToolOutputRecord`를 지원하며, generation-checked `PlanningContext v3`는 Step의 plan journal 순서와 passed Receipt에 결합된 exact ToolOutput의 관찰 순서를 보존해 다음 model turn에 전달합니다. NonIdempotent durable output은 결과만 보존하고 Started 뒤 불확정 effect의 no-replay 의미는 유지합니다. Public `xgen run/resume`은 기존 exact `--allow-file` mode를 보존하면서, 명시적 `--allow-dir` mode에서 네 read Capability와 `write-atomic`·`apply-patch`로 workspace를 탐색·수정합니다. Host가 등록한 executable은 별도 승인 뒤 shell 없이 bounded cwd·argv·환경·timeout·출력 제한으로 실행하며 결과를 durable하게 보존하고 불확정 실행을 자동 반복하지 않습니다. Dynamic path/query/write/process material은 Run 전용 embedded `materials.sqlite3`에 digest-bound recipe로 저장되어 approval pause 뒤 다른 process에서도 복원됩니다. 모델 프로필은 일반 설정과 OS 보안 credential 저장소를 분리하고 catalog와 실제 strict-JSON inference를 모두 검증합니다. Bare `xgen`는 이 경계 위에서 Thinking 표시와 검증된 최종 응답을 제공하고 model/read/write/execute를 각각 묻는 가벼운 REPL을 시작합니다. 로컬 JSON OpenAPI 명세로 연결한 제한된 HTTP GET 조회는 지원합니다. 지원 범위는 [HTTP 조회 문서](docs/development/http-read-tools.md)를 따르며, HTTP 쓰기·범용 MCP 클라이언트·XGEN compatibility adapter는 후속 범위입니다.
> 현재 상태: 프로토콜 v0.1 검증, dependency DAG와 Receipt-gated 재개 frontier를 갖춘 model-free WorkGraph, embedded SQLite schema 8 store, effect 실행·복구 조정기, 결정론적 Capability Registry·Router, I/O 없는 Permission Broker, secret-free invocation material 복구, Direct Executor와 Core-owned Execution Receipt 검증 경계, 장기 Run용 VerifiedRunIndex를 실행할 수 있습니다. Provider-neutral bounded AgentLoop는 비신뢰 `PlanProposal`을 검증해 다중 Step DAG와 reconstructable input sidecar를 원자 commit하고 재시작 뒤 한 frontier action씩 이어갑니다. Model call은 provider 호출 전에 보수적인 possible-send slot을 journal에 예약하며 accepted/rejected/Unknown을 재시작 뒤 복원하고, 불확정 호출을 자동 재시도하지 않습니다. 별도 `xgen-provider-openai` leaf adapter는 OpenAI-compatible Chat Completions의 단일 요청과 strict structured proposal을 이 lifecycle에 연결합니다. 내부 WorkGraph는 planned `ReadOnly`/`Idempotent`/`NonIdempotent`, Core Receipt v2의 bounded Artifact commitment와 event-anchored typed `ToolOutputRecord`를 지원하며, generation-checked `PlanningContext v3`는 Step의 plan journal 순서와 passed Receipt에 결합된 exact ToolOutput의 관찰 순서를 보존해 다음 model turn에 전달합니다. NonIdempotent durable output은 결과만 보존하고 Started 뒤 불확정 effect의 no-replay 의미는 유지합니다. Public `xgen run/resume`은 기존 exact `--allow-file` mode를 보존하면서, 명시적 `--allow-dir` mode에서 네 read Capability와 `write-atomic`·`apply-patch`로 workspace를 탐색·수정합니다. Host가 등록한 executable은 별도 승인 뒤 shell 없이 bounded cwd·argv·환경·timeout·출력 제한으로 실행하며 결과를 durable하게 보존하고 불확정 실행을 자동 반복하지 않습니다. Dynamic path/query/write/process material은 Run 전용 embedded `materials.sqlite3`에 digest-bound recipe로 저장되어 approval pause 뒤 다른 process에서도 복원됩니다. 모델 프로필은 일반 설정과 OS 보안 credential 저장소를 분리하고 catalog와 실제 strict-JSON inference를 모두 검증합니다. Bare `xgen`는 이 경계 위에서 Thinking 표시와 검증된 최종 응답을 제공하고 model/read/write/execute를 각각 묻는 가벼운 REPL을 시작합니다. 로컬 JSON OpenAPI 명세로 연결한 제한된 HTTP GET·호스트 지정 POST 조회는 지원합니다. 지원 범위는 [HTTP 조회 문서](docs/development/http-read-tools.md)를 따르며, HTTP 쓰기·범용 MCP 클라이언트·XGEN compatibility adapter는 후속 범위입니다.

## 설치와 첫 모델 연결

Expand Down
4 changes: 2 additions & 2 deletions crates/xgen-cli/src/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,7 @@ fn continue_incomplete(
);
}
if discovery.has_connections() {
planning_constraints.push(PlanningConstraint::new("http.read-connections", "Host-approved GET connections are available through xgen.http/read. First describe the tool, check http_read.supported, use its exact contract_digest as contractDigest, and pass parameters.path and parameters.query. A successful HTTP Receipt and schema-valid body are required before claiming API results. Credentials are resolved only by the host.").map_err(|_| PublicRunError::Internal)?);
planning_constraints.push(PlanningConstraint::new("http.read-connections", "Host-approved read connections are available through xgen.http/read. First describe the tool, check http_read.supported, use its exact contract_digest as contractDigest, and pass parameters.path and parameters.query. For explicitly host-approved POST reads, also pass body according to http_read.request_body. A successful HTTP Receipt and schema-valid body are required before claiming API results. Credentials are resolved only by the host.").map_err(|_| PublicRunError::Internal)?);
}
if let Some(process) = process {
planning_constraints.push(
Expand Down Expand Up @@ -3142,7 +3142,7 @@ fn execution_profile_with_discovery(
"material_provider":graph_discovery::PROVIDER,
"material_recipe_domain":"xgen.cli.tool-discovery-recipe/v1",
"host_policy_profile":"xgen.cli.host-tool-discovery/v1",
"http_read_profile": if connections.is_empty() { serde_json::Value::Null } else { serde_json::json!({"profile":crate::http_read::PROFILE,"get_only":true,"max_body_bytes":32768,"timeout_seconds":30,"redirects":false,"proxy":false}) },
"http_read_profile": if connections.is_empty() { serde_json::Value::Null } else { serde_json::json!({"profile":crate::http_read::PROFILE,"host_selected_post":true,"max_body_bytes":32768,"timeout_seconds":30,"redirects":false,"proxy":false}) },
});
if connections.is_empty() {
profile
Expand Down
8 changes: 6 additions & 2 deletions crates/xgen-cli/src/graph_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub(crate) const SEARCH: &str = "xgen.tools/search";
pub(crate) const DESCRIBE: &str = "xgen.tools/describe";
pub(crate) const VERSION: &str = "1.0.0";
pub(crate) fn version(operation: &str) -> &'static str {
if operation == DESCRIBE {
if matches!(operation, DESCRIBE | crate::http_read::READ) {
"2.0.0"
} else {
VERSION
Expand Down Expand Up @@ -205,7 +205,7 @@ impl DiscoveryCatalog {
}
pub(crate) fn execution_contract(&self, name: &str, tool: &str) -> Result<Value, &'static str> {
let output = crate::tools::invoke_offline(
&json!({"operation":"execution_contract","root":self.root.to_str().ok_or("invalid_state_home")?,"name":name,"tool":tool,"expected_digest":self.snapshots.get(name).ok_or("collection_not_found")?}),
&json!({"http_policy":self.connection(name).map(crate::http_read::Connection::policy),"operation":"execution_contract","root":self.root.to_str().ok_or("invalid_state_home")?,"name":name,"tool":tool,"expected_digest":self.snapshots.get(name).ok_or("collection_not_found")?}),
)?;
// Full execution contracts stay on the host; the model's 64 KiB limit
// applies only to discovery observations, never to schema validation.
Expand Down Expand Up @@ -338,6 +338,10 @@ impl DiscoveryAdapter {
request["query"] = input["query"].clone();
request["top_k"] = input["topK"].clone();
} else {
request["http_policy"] = self
.catalog
.connection(name)
.map_or(Value::Null, crate::http_read::Connection::policy);
request["tool"] = input["tool"].clone();
request["parameter_offset"] = input.get("parameterOffset").cloned().unwrap_or(json!(0));
}
Expand Down
Loading