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
2 changes: 2 additions & 0 deletions crates/ov_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ ov glob "**/*.md" --uri viking://resources
# Session workflow
SESSION=$(ov -o json session new | jq -r '.result.session_id')
ov session add-message --session-id $SESSION --role user --content "Hello"
ov session add-message --session-id $SESSION --role user --content "remember this" \
--auto-commit-enabled true --token-threshold 512 --idle-timeout-seconds 60 --keep-recent-count 2
ov session commit --session-id $SESSION
```

Expand Down
109 changes: 107 additions & 2 deletions crates/ov_cli/src/commands/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,49 @@ fn message_body(client: &HttpClient, role: &str, content: &str) -> serde_json::V
body
}

fn apply_auto_commit_policy(
body: &mut serde_json::Value,
enabled: Option<bool>,
token_threshold: Option<u32>,
idle_timeout_seconds: Option<u32>,
keep_recent_count: Option<u32>,
) {
if enabled.is_none()
&& token_threshold.is_none()
&& idle_timeout_seconds.is_none()
&& keep_recent_count.is_none()
{
return;
}
let mut policy = serde_json::Map::new();
if let Some(enabled) = enabled {
policy.insert("enabled".to_string(), json!(enabled));
}
if let Some(token_threshold) = token_threshold {
policy.insert("token_threshold".to_string(), json!(token_threshold));
}
if let Some(idle_timeout_seconds) = idle_timeout_seconds {
policy.insert(
"idle_timeout_seconds".to_string(),
json!(idle_timeout_seconds),
);
}
if let Some(keep_recent_count) = keep_recent_count {
policy.insert("keep_recent_count".to_string(), json!(keep_recent_count));
}
body["auto_commit_policy"] = serde_json::Value::Object(policy);
}

pub async fn add_message(
client: &HttpClient,
session_id: &str,
role: &str,
content: &str,
peer_id: Option<&str>,
auto_commit_enabled: Option<bool>,
token_threshold: Option<u32>,
idle_timeout_seconds: Option<u32>,
keep_recent_count: Option<u32>,
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
Expand All @@ -327,6 +364,13 @@ pub async fn add_message(
body["agent_id"] = json!(agent_id);
}
}
apply_auto_commit_policy(
&mut body,
auto_commit_enabled,
token_threshold,
idle_timeout_seconds,
keep_recent_count,
);

let response: serde_json::Value = client.post(&path, &body).await?;
output_success(&response, output_format, compact);
Expand All @@ -337,6 +381,10 @@ pub async fn add_messages(
client: &HttpClient,
session_id: &str,
input: &str,
auto_commit_enabled: Option<bool>,
token_threshold: Option<u32>,
idle_timeout_seconds: Option<u32>,
keep_recent_count: Option<u32>,
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
Expand All @@ -346,7 +394,14 @@ pub async fn add_messages(
.iter()
.map(|(role, content)| message_body(client, role, content))
.collect();
let body = json!({"messages": messages_json});
let mut body = json!({"messages": messages_json});
apply_auto_commit_policy(
&mut body,
auto_commit_enabled,
token_threshold,
idle_timeout_seconds,
keep_recent_count,
);
let response: serde_json::Value = client.post(&path, &body).await?;
output_success(&response, output_format, compact);
Ok(())
Expand Down Expand Up @@ -429,7 +484,8 @@ fn url_encode(s: &str) -> String {

#[cfg(test)]
mod tests {
use super::{parse_messages, render_session_get_for_table};
use super::{apply_auto_commit_policy, message_body, parse_messages, render_session_get_for_table};
use crate::client::HttpClient;
use crate::error::Error;
use serde_json::json;

Expand Down Expand Up @@ -529,6 +585,55 @@ mod tests {
assert!(render_session_get_for_table(&result).is_none());
}

#[test]
fn apply_auto_commit_policy_skips_empty_policy() {
let mut body = json!({"role": "user", "content": "hello"});

apply_auto_commit_policy(&mut body, None, None, None, None);

assert!(body.get("auto_commit_policy").is_none());
}

#[test]
fn apply_auto_commit_policy_adds_top_level_policy() {
let mut body = json!({"messages": [{"role": "user", "content": "hello"}]});

apply_auto_commit_policy(&mut body, Some(true), Some(128), Some(30), Some(2));

assert_eq!(
body,
json!({
"messages": [{"role": "user", "content": "hello"}],
"auto_commit_policy": {
"enabled": true,
"token_threshold": 128,
"idle_timeout_seconds": 30,
"keep_recent_count": 2
}
})
);
}

#[test]
fn message_body_preserves_message_shape_without_auto_commit_policy() {
let client = HttpClient::new(
"http://localhost:1933".to_string(),
None,
None,
None,
None,
None,
5.0,
false,
None,
);

let body = message_body(&client, "user", "hello");

assert_eq!(body, json!({"role": "user", "content": "hello"}));
assert!(body.get("auto_commit_policy").is_none());
}

fn strip_ansi(input: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
Expand Down
16 changes: 16 additions & 0 deletions crates/ov_cli/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,13 +464,21 @@ pub async fn handle_session(cmd: SessionCommands, ctx: CliContext) -> Result<()>
role,
content,
peer_id,
auto_commit_enabled,
token_threshold,
idle_timeout_seconds,
keep_recent_count,
} => {
commands::session::add_message(
&client,
&session_id,
&role,
&content,
peer_id.as_deref(),
auto_commit_enabled,
token_threshold,
idle_timeout_seconds,
keep_recent_count,
ctx.output_format,
ctx.compact,
)
Expand All @@ -479,11 +487,19 @@ pub async fn handle_session(cmd: SessionCommands, ctx: CliContext) -> Result<()>
SessionCommands::AddMessages {
session_id,
messages,
auto_commit_enabled,
token_threshold,
idle_timeout_seconds,
keep_recent_count,
} => {
commands::session::add_messages(
&client,
&session_id,
&messages,
auto_commit_enabled,
token_threshold,
idle_timeout_seconds,
keep_recent_count,
ctx.output_format,
ctx.compact,
)
Expand Down
24 changes: 24 additions & 0 deletions crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,18 @@ enum SessionCommands {
/// Stable interaction peer id. Omit for self memory.
#[arg(long = "peer-id", value_name = "peer-id")]
peer_id: Option<String>,
/// Enable or disable session auto commit.
#[arg(long = "auto-commit-enabled", value_name = "bool")]
auto_commit_enabled: Option<bool>,
/// Trigger auto commit when pending tokens reach this threshold.
#[arg(long = "token-threshold", value_name = "tokens")]
token_threshold: Option<u32>,
/// Trigger auto commit after this idle timeout in seconds.
#[arg(long = "idle-timeout-seconds", value_name = "seconds")]
idle_timeout_seconds: Option<u32>,
/// Keep this many recent live messages after commit.
#[arg(long = "keep-recent-count", value_name = "count")]
keep_recent_count: Option<u32>,
},
/// Add multiple messages to a session
AddMessages {
Expand All @@ -1141,6 +1153,18 @@ enum SessionCommands {
/// Messages as JSON array of {role, content} objects
#[arg(value_name = "messages-json")]
messages: String,
/// Enable or disable session auto commit.
#[arg(long = "auto-commit-enabled", value_name = "bool")]
auto_commit_enabled: Option<bool>,
/// Trigger auto commit when pending tokens reach this threshold.
#[arg(long = "token-threshold", value_name = "tokens")]
token_threshold: Option<u32>,
/// Trigger auto commit after this idle timeout in seconds.
#[arg(long = "idle-timeout-seconds", value_name = "seconds")]
idle_timeout_seconds: Option<u32>,
/// Keep this many recent live messages after commit.
#[arg(long = "keep-recent-count", value_name = "count")]
keep_recent_count: Option<u32>,
},
/// Commit a session (archive messages and extract memories)
Commit {
Expand Down
22 changes: 21 additions & 1 deletion docs/en/api/05-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,13 +654,29 @@ Add a message to the session. Supports two modes: simple text mode and Parts mod
| content | str | Conditional | - | Message text content (HTTP API simple mode, mutually exclusive with parts) |
| created_at | str | No | None | Optional ISO 8601 timestamp to persist on the message |
| peer_id | str | No | None | Optional stable interaction peer identity |
| auto_commit_policy | object | No | None | Optional session-level auto-commit policy. When provided, it is persisted into session metadata and reused by later server-side automatic triggers |

> **Note**: HTTP API supports two modes:
> 1. **Simple mode**: Use `content` string (backward compatible)
> 2. **Parts mode**: Use `parts` array (full Part support)
>
> If both `content` and `parts` are provided, `parts` takes precedence.

`auto_commit_policy` fields:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `enabled` | bool | No | Enables or disables automatic commit for the session. Setting it to `false` turns off auto-triggering for that session |
| `token_threshold` | int | No | Token threshold. After a message write, OpenViking tries an immediate auto commit when `pending_tokens` reaches this value |
| `idle_timeout_seconds` | int | No | Idle timeout in seconds. When configured, the session becomes eligible for the server-side idle scheduler |
| `keep_recent_count` | int | No | Number of recent live messages to keep after commit. Updating it also updates session metadata and rebuilds `pending_tokens` |

Additional notes:

- `auto_commit_policy` is a session-level policy, not a per-message policy.
- Once written, later server-side automatic triggering uses the persisted value from session metadata.
- `idle_timeout_seconds` only takes effect when the server-wide `server.session_auto_commit.idle_enabled` switch is enabled.

**Part Types (Python SDK)**

```python
Expand Down Expand Up @@ -849,9 +865,13 @@ Add multiple messages to a session in a single request. Suitable for scenarios t
|------|------|------|--------|------|
| session_id | str | Yes | - | Session ID |
| messages | List[AddMessageRequest] | Yes | - | List of messages, each following the same format as `add_message()`, max 100 |
| auto_commit_policy | object | No | None | Optional session-level auto-commit policy. It may only be provided once at the batch top level |
| telemetry | bool | No | False | Whether to attach operation telemetry data |

> **Note**: Each message follows the exact same format as `add_message()`, supporting both `content` (simple mode) and `parts` (Parts mode). If you need to add more than 100 messages, call in batches.
> **Note**:
> 1. Each message supports both `content` (simple mode) and `parts` (Parts mode). If you need to add more than 100 messages, call in batches.
> 2. `auto_commit_policy` is only allowed at the batch top level.
> 3. `messages[*].auto_commit_policy` is rejected by the server.

#### 3. Usage Examples

Expand Down
29 changes: 29 additions & 0 deletions docs/en/guides/01-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,35 @@ Legacy compatibility example:
}
```

##### Session Auto Commit Configuration

`server.session_auto_commit` controls server-wide automatic session commit behavior.

```json
{
"server": {
"session_auto_commit": {
"idle_enabled": true,
"check_interval_seconds": 60.0
}
}
}
```

| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| `idle_enabled` | bool | Enables the server-side idle-timeout auto-commit scheduler. When disabled, the idle scheduler is not started and the idle index is not maintained. Token-threshold immediate triggering still works | `true` |
| `check_interval_seconds` | float | Poll interval for the idle scheduler in seconds. Must be greater than `0` | `60.0` |

Notes:

- `server.session_auto_commit` is a server-wide control surface, not a per-session business policy.
- Per-session auto-commit behavior is configured through `auto_commit_policy` on message write APIs and persisted into session metadata.
- When `idle_enabled=false`:
- `SessionAutoCommitScheduler` is not started
- `/local/_system/session_auto_commit/index.json` is not maintained
- Token-threshold auto commit does not depend on the scheduler and is unaffected by this switch.


##### S3 Backend Configuration

Expand Down
22 changes: 21 additions & 1 deletion docs/zh/api/05-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -653,13 +653,29 @@ ov session delete a1b2c3d4
| content | str | 条件必填 | - | 消息文本内容(HTTP API 简单模式,与 parts 二选一) |
| created_at | str | 否 | None | 可选的 ISO 8601 时间戳,会原样保存到消息中 |
| peer_id | str | 否 | None | 可选的稳定交互对象 ID |
| auto_commit_policy | object | 否 | None | 可选的 session 级自动 commit 策略。传入后会持久化到 session meta,并用于后续服务端自动触发 |

> **注意**:HTTP API 支持两种模式:
> 1. **简单模式**:使用 `content` 字符串(向后兼容)
> 2. **Parts 模式**:使用 `parts` 数组(完整 Part 支持)
>
> 如果同时提供 `content` 和 `parts`,`parts` 优先。

`auto_commit_policy` 字段:

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `enabled` | bool | 否 | 是否启用该 session 的自动 commit。设为 `false` 时会关闭该 session 的自动触发 |
| `token_threshold` | int | 否 | token 阈值。消息写入后若 `pending_tokens` 达到该值,会尝试立即触发自动 commit |
| `idle_timeout_seconds` | int | 否 | idle 超时时间,单位秒。配置后该 session 会进入服务端 idle 调度范围 |
| `keep_recent_count` | int | 否 | commit 后保留最近多少条 live message,不参与归档删除。修改后会同步更新 session meta,并触发 `pending_tokens` 重建 |

补充说明:

- `auto_commit_policy` 是 session 级配置,不是单条 message 级配置。
- 一旦写入,后续服务端自动触发以 session meta 中的持久化值为准。
- `idle_timeout_seconds` 是否生效,还取决于服务端全局配置 `server.session_auto_commit.idle_enabled` 是否开启。

**Part 类型(Python SDK)**

```python
Expand Down Expand Up @@ -823,9 +839,13 @@ ov session add-message a1b2c3d4 --role user --content "How do I authenticate use
|------|------|------|--------|------|
| session_id | str | 是 | - | 会话 ID |
| messages | List[AddMessageRequest] | 是 | - | 消息列表,每条消息格式与 `add_message()` 相同,最多 100 条 |
| auto_commit_policy | object | 否 | None | 可选的 session 级自动 commit 策略。只允许在 batch 顶层传一次 |
| telemetry | bool | 否 | False | 是否附加操作遥测数据 |

> **注意**:每条消息的格式与 `add_message()` 完全一致,支持 `content`(简单模式)和 `parts`(Parts 模式)。超过 100 条需分批调用。
> **注意**:
> 1. 每条消息支持 `content`(简单模式)和 `parts`(Parts 模式),超过 100 条需分批调用。
> 2. `auto_commit_policy` 只允许出现在 batch 顶层。
> 3. `messages[*].auto_commit_policy` 不允许,服务端会直接拒绝请求。

#### 3. 使用示例

Expand Down
Loading
Loading