rename: adopt yoi identity

This commit is contained in:
Keisuke Hirata 2026-06-01 18:49:23 +09:00
parent 6e133a7229
commit e6c458021c
No known key found for this signature in database
115 changed files with 945 additions and 732 deletions

View File

@ -23,7 +23,7 @@ Podの状態から純粋に再現可能で、且つ揮発性の無い操作で
## 実際のセッションを読んでデバッグする ## 実際のセッションを読んでデバッグする
`~/.insomnia/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。 `~/.yoi/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。
--- ---
@ -74,4 +74,4 @@ worktree と併用して作業を進める場合、必ずブランチを切る
--- ---
insomniaでinsomniaを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。 YoiでYoiを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。

31
Cargo.lock generated
View File

@ -1479,21 +1479,6 @@ dependencies = [
"rustversion", "rustversion",
] ]
[[package]]
name = "insomnia"
version = "0.1.0"
dependencies = [
"client",
"memory",
"pod",
"serde",
"serde_json",
"session-store",
"tempfile",
"tokio",
"tui",
]
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.12" version = "0.3.12"
@ -4761,6 +4746,22 @@ dependencies = [
"markup5ever", "markup5ever",
] ]
[[package]]
name = "yoi"
version = "0.1.0"
dependencies = [
"client",
"manifest",
"memory",
"pod",
"serde",
"serde_json",
"session-store",
"tempfile",
"tokio",
"tui",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.2" version = "0.8.2"

View File

@ -9,7 +9,7 @@ members = [
"crates/secrets", "crates/secrets",
"crates/manifest", "crates/manifest",
"crates/pod", "crates/pod",
"crates/insomnia", "crates/yoi",
"crates/pod-store", "crates/pod-store",
"crates/protocol", "crates/protocol",
"crates/provider", "crates/provider",
@ -35,7 +35,7 @@ manifest = { path = "crates/manifest" }
lint-common = { path = "crates/lint-common" } lint-common = { path = "crates/lint-common" }
memory = { path = "crates/memory" } memory = { path = "crates/memory" }
pod = { path = "crates/pod" } pod = { path = "crates/pod" }
insomnia = { path = "crates/insomnia" } yoi = { path = "crates/yoi" }
pod-registry = { path = "crates/pod-registry" } pod-registry = { path = "crates/pod-registry" }
pod-store = { path = "crates/pod-store" } pod-store = { path = "crates/pod-store" }
protocol = { path = "crates/protocol" } protocol = { path = "crates/protocol" }

View File

@ -1,5 +1,5 @@
# INSOMNIA # 夜居 | Yoi agent
insomnia(i6a)は不休のエージェントループを回すためのエージェントプラットフォーム。 夜居(Yoi)は不休のエージェントループを回すためのエージェントプラットフォーム。
ワークフローを統括し、四六時中電力を消費し、イテレーションします。 ワークフローを統括し、四六時中電力を消費し、イテレーションします。

View File

@ -2,7 +2,7 @@
//! //!
//! - [`PodClient`]: 既存 pod の Unix ソケットへ接続して `Method` を送り、 //! - [`PodClient`]: 既存 pod の Unix ソケットへ接続して `Method` を送り、
//! `Event` を受け取る低レベル接続。 //! `Event` を受け取る低レベル接続。
//! - [`spawn`]: pod バイナリをサブプロセスとして起動し、`INSOMNIA-READY` //! - [`spawn`]: pod バイナリをサブプロセスとして起動し、`YOI-READY`
//! ハンドシェイクが終わるまで待つフロー。subprocess を立ち上げる必要が //! ハンドシェイクが終わるまで待つフロー。subprocess を立ち上げる必要が
//! ない呼び出し側 (=既存 pod に attach する場合) は使わなくてよい。 //! ない呼び出し側 (=既存 pod に attach する場合) は使わなくてよい。
//! //!

View File

@ -3,7 +3,7 @@ use std::fmt;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
const POD_RUNTIME_COMMAND_ENV: &str = "INSOMNIA_POD_RUNTIME_COMMAND"; const POD_RUNTIME_COMMAND_ENV: &str = "YOI_POD_RUNTIME_COMMAND";
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct PodRuntimeCommand { pub struct PodRuntimeCommand {
@ -29,9 +29,9 @@ impl PodRuntimeCommand {
/// Resolve the Pod runtime command used for subprocess launches. /// Resolve the Pod runtime command used for subprocess launches.
/// ///
/// The default launch path is always the current `insomnia` executable plus /// The default launch path is always the current `yoi` executable plus
/// the unified `pod` prefix argument. During development, a non-empty /// the unified `pod` prefix argument. During development, a non-empty
/// `INSOMNIA_POD_RUNTIME_COMMAND` value replaces only the executable path; /// `YOI_POD_RUNTIME_COMMAND` value replaces only the executable path;
/// the `pod` prefix is still added here and the env value is not parsed as a /// the `pod` prefix is still added here and the env value is not parsed as a
/// shell command. /// shell command.
pub fn resolve() -> io::Result<Self> { pub fn resolve() -> io::Result<Self> {
@ -89,10 +89,10 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn insomnia_binary_defaults_to_pod_prefix() { fn yoi_binary_defaults_to_pod_prefix() {
let command = PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia"); let command = PodRuntimeCommand::for_executable("/opt/yoi/bin/yoi");
assert_eq!(command.program(), Path::new("/opt/insomnia/bin/insomnia")); assert_eq!(command.program(), Path::new("/opt/yoi/bin/yoi"));
assert_eq!(command.prefix_args(), [OsString::from("pod")]); assert_eq!(command.prefix_args(), [OsString::from("pod")]);
assert_eq!( assert_eq!(
command.argv_with(["--pod", "agent"]), command.argv_with(["--pod", "agent"]),
@ -105,12 +105,9 @@ mod tests {
#[test] #[test]
fn any_runtime_executable_gets_pod_prefix() { fn any_runtime_executable_gets_pod_prefix() {
let command = PodRuntimeCommand::for_executable("/opt/insomnia/bin/custom-runtime"); let command = PodRuntimeCommand::for_executable("/opt/yoi/bin/custom-runtime");
assert_eq!( assert_eq!(command.program(), Path::new("/opt/yoi/bin/custom-runtime"));
command.program(),
Path::new("/opt/insomnia/bin/custom-runtime")
);
assert_eq!(command.prefix_args(), [OsString::from("pod")]); assert_eq!(command.prefix_args(), [OsString::from("pod")]);
assert_eq!( assert_eq!(
command.argv_with(["--pod", "agent"]), command.argv_with(["--pod", "agent"]),
@ -124,38 +121,38 @@ mod tests {
#[test] #[test]
fn resolve_uses_current_exe_when_override_is_unset() { fn resolve_uses_current_exe_when_override_is_unset() {
let command = PodRuntimeCommand::resolve_from_env_value(None, || { let command = PodRuntimeCommand::resolve_from_env_value(None, || {
Ok(PathBuf::from("/opt/insomnia/bin/insomnia")) Ok(PathBuf::from("/opt/yoi/bin/yoi"))
}) })
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
command, command,
PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia") PodRuntimeCommand::for_executable("/opt/yoi/bin/yoi")
); );
} }
#[test] #[test]
fn resolve_uses_current_exe_when_override_is_empty() { fn resolve_uses_current_exe_when_override_is_empty() {
let command = PodRuntimeCommand::resolve_from_env_value(Some(OsString::new()), || { let command = PodRuntimeCommand::resolve_from_env_value(Some(OsString::new()), || {
Ok(PathBuf::from("/opt/insomnia/bin/insomnia")) Ok(PathBuf::from("/opt/yoi/bin/yoi"))
}) })
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
command, command,
PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia") PodRuntimeCommand::for_executable("/opt/yoi/bin/yoi")
); );
} }
#[test] #[test]
fn resolve_override_replaces_only_program_and_keeps_pod_prefix() { fn resolve_override_replaces_only_program_and_keeps_pod_prefix() {
let command = PodRuntimeCommand::resolve_from_env_value( let command = PodRuntimeCommand::resolve_from_env_value(
Some(OsString::from("/tmp/rebuilt insomnia")), Some(OsString::from("/tmp/rebuilt yoi")),
|| panic!("override must not inspect current_exe"), || panic!("override must not inspect current_exe"),
) )
.unwrap(); .unwrap();
assert_eq!(command.program(), Path::new("/tmp/rebuilt insomnia")); assert_eq!(command.program(), Path::new("/tmp/rebuilt yoi"));
assert_eq!(command.prefix_args(), [OsString::from("pod")]); assert_eq!(command.prefix_args(), [OsString::from("pod")]);
assert_eq!( assert_eq!(
command.argv_with(["--pod", "agent"]), command.argv_with(["--pod", "agent"]),

View File

@ -1,9 +1,9 @@
//! Pod runtime command をサブプロセスとして立ち上げ、`INSOMNIA-READY` を待つ //! Pod runtime command をサブプロセスとして立ち上げ、`YOI-READY` を待つ
//! ハンドシェイク。 //! ハンドシェイク。
//! //!
//! - 親プロセス (TUI / GUI / E2E) は profile/default/typed restore flags を //! - 親プロセス (TUI / GUI / E2E) は profile/default/typed restore flags を
//! 指定してこの関数に渡す。pod はそれを受けて socket を bind し、stderr に //! 指定してこの関数に渡す。pod はそれを受けて socket を bind し、stderr に
//! `INSOMNIA-READY\t<name>\t<socket>` を吐く。 //! `YOI-READY\t<name>\t<socket>` を吐く。
//! - 待機中の stderr 行は `progress` コールバック越しに呼び出し側へ流す。 //! - 待機中の stderr 行は `progress` コールバック越しに呼び出し側へ流す。
//! UI の進捗表示や E2E のログ収集はここで賄う。 //! UI の進捗表示や E2E のログ収集はここで賄う。
//! - `kill_on_drop = false` + `process_group(0)` により、親プロセス //! - `kill_on_drop = false` + `process_group(0)` により、親プロセス
@ -19,7 +19,7 @@ use crate::PodRuntimeCommand;
use tokio::process::Command; use tokio::process::Command;
use uuid::Uuid; use uuid::Uuid;
const READY_PREFIX: &str = "INSOMNIA-READY\t"; const READY_PREFIX: &str = "YOI-READY\t";
const READY_TIMEOUT: Duration = Duration::from_secs(20); const READY_TIMEOUT: Duration = Duration::from_secs(20);
/// `spawn_pod` の入力。 /// `spawn_pod` の入力。
@ -69,7 +69,7 @@ impl std::fmt::Display for SpawnError {
Self::Io(e) => write!(f, "io error: {e}"), Self::Io(e) => write!(f, "io error: {e}"),
Self::RuntimeDirUnavailable => write!( Self::RuntimeDirUnavailable => write!(
f, f,
"could not resolve runtime directory (set INSOMNIA_HOME, INSOMNIA_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)" "could not resolve runtime directory (set YOI_HOME, YOI_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
), ),
Self::PodLaunchFailed { command, source } => write!( Self::PodLaunchFailed { command, source } => write!(
f, f,
@ -106,7 +106,7 @@ impl From<io::Error> for SpawnError {
} }
} }
/// pod を spawn し、`INSOMNIA-READY` ハンドシェイクが終わるまで待つ。 /// pod を spawn し、`YOI-READY` ハンドシェイクが終わるまで待つ。
/// ///
/// `progress` は ready 行を見つけるまでに観測した stderr の各行で呼ばれる /// `progress` は ready 行を見つけるまでに観測した stderr の各行で呼ばれる
/// (ready 行自体は除外される)。UI の表示更新や E2E ログ取得に使う。 /// (ready 行自体は除外される)。UI の表示更新や E2E ログ取得に使う。

View File

@ -2,7 +2,7 @@
//! //!
//! `response.*` 名前空間の SSE を共通の [`Event`](crate::llm_client::event::Event) //! `response.*` 名前空間の SSE を共通の [`Event`](crate::llm_client::event::Event)
//! に変換する。Responses の (output_index, content_index) 2 次元座標と //! に変換する。Responses の (output_index, content_index) 2 次元座標と
//! insomnia 側 1 次元 `BlockStart/Delta/Stop::index` のマッピングは //! yoi 側 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
//! [`OpenAIResponsesState`] が保持する。 //! [`OpenAIResponsesState`] が保持する。
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};

View File

@ -1,6 +1,6 @@
//! LLM Client Common Types //! LLM Client Common Types
//! //!
//! Core conversation types for insomnia's LLM interaction model. //! Core conversation types for yoi's LLM interaction model.
//! The core abstraction is `Item` which represents different types of conversation elements: //! The core abstraction is `Item` which represents different types of conversation elements:
//! - Message items (user/assistant messages with content parts) //! - Message items (user/assistant messages with content parts)
//! - ToolCall items (tool invocations) //! - ToolCall items (tool invocations)

View File

@ -688,7 +688,7 @@ mod tests {
use crate::{Permission, ReasoningEffort, ScopeRule}; use crate::{Permission, ReasoningEffort, ScopeRule};
fn abs(path: &str) -> PathBuf { fn abs(path: &str) -> PathBuf {
PathBuf::from(format!("/tmp/insomnia-test{path}")) PathBuf::from(format!("/tmp/yoi-test{path}"))
} }
fn api_key_file_auth(path: PathBuf) -> AuthRef { fn api_key_file_auth(path: PathBuf) -> AuthRef {
@ -791,14 +791,14 @@ mod tests {
fn resolve_paths_joins_relative_auth_file() { fn resolve_paths_joins_relative_auth_file() {
let mut cfg = minimal_valid(); let mut cfg = minimal_valid();
cfg.model.auth = Some(api_key_file_auth(PathBuf::from("keys/anthropic"))); cfg.model.auth = Some(api_key_file_auth(PathBuf::from("keys/anthropic")));
let resolved = cfg.resolve_paths(Path::new("/home/user/.config/insomnia")); let resolved = cfg.resolve_paths(Path::new("/home/user/.config/yoi"));
let file = match resolved.model.auth { let file = match resolved.model.auth {
Some(AuthRef::ApiKey { file, .. }) => file, Some(AuthRef::ApiKey { file, .. }) => file,
_ => panic!("expected ApiKey"), _ => panic!("expected ApiKey"),
}; };
assert_eq!( assert_eq!(
file.as_deref(), file.as_deref(),
Some(Path::new("/home/user/.config/insomnia/keys/anthropic")) Some(Path::new("/home/user/.config/yoi/keys/anthropic"))
); );
} }

View File

@ -44,8 +44,8 @@ pub const COMPACT_OVERVIEW_DEADLINE_TOKENS: u64 = 40_000;
/// Default instruction asset reference used when `worker.instruction` /// Default instruction asset reference used when `worker.instruction`
/// is omitted. See the `PromptLoader` prefix addressing scheme for the /// is omitted. See the `PromptLoader` prefix addressing scheme for the
/// `$insomnia/` / `$user/` / `$workspace/` namespaces. /// `$yoi/` / `$user/` / `$workspace/` namespaces.
pub const DEFAULT_INSTRUCTION: &str = "$insomnia/default"; pub const DEFAULT_INSTRUCTION: &str = "$yoi/default";
/// Default language policy used by the main worker for normal prose /// Default language policy used by the main worker for normal prose
/// responses. See [`crate::WorkerManifest::language`]. /// responses. See [`crate::WorkerManifest::language`].

View File

@ -256,7 +256,7 @@ pub struct PodMeta {
pub struct WorkerManifest { pub struct WorkerManifest {
/// Reference to the instruction prompt asset used as the body of /// Reference to the instruction prompt asset used as the body of
/// the worker's system prompt. Uses the `PromptLoader` prefix /// the worker's system prompt. Uses the `PromptLoader` prefix
/// addressing scheme (`$insomnia/...`, `$user/...`, /// addressing scheme (`$yoi/...`, `$user/...`,
/// `$workspace/...`) and is always populated after resolution — /// `$workspace/...`) and is always populated after resolution —
/// unset manifests fall through to [`defaults::DEFAULT_INSTRUCTION`]. /// unset manifests fall through to [`defaults::DEFAULT_INSTRUCTION`].
#[serde(default = "default_instruction")] #[serde(default = "default_instruction")]

View File

@ -1,4 +1,4 @@
//! Insomnia のホームディレクトリ配下のパス解決を一元化するモジュール。 //! Yoi のホームディレクトリ配下のパス解決を一元化するモジュール。
//! //!
//! 用途別に三つの base directory を持つ: //! 用途別に三つの base directory を持つ:
//! //!
@ -10,13 +10,13 @@
//! //!
//! ## 解決順 (優先順位高 → 低) //! ## 解決順 (優先順位高 → 低)
//! //!
//! | base | 1. `INSOMNIA_<KIND>_DIR` | 2. `INSOMNIA_HOME` | 3. `XDG_*` | 4. 既定 | //! | base | 1. `YOI_<KIND>_DIR` | 2. `YOI_HOME` | 3. `XDG_*` | 4. 既定 |
//! |---|---|---|---|---| //! |---|---|---|---|---|
//! | config | `INSOMNIA_CONFIG_DIR` | `$INSOMNIA_HOME/config` | `$XDG_CONFIG_HOME/insomnia` | `$HOME/.config/insomnia` | //! | config | `YOI_CONFIG_DIR` | `$YOI_HOME/config` | `$XDG_CONFIG_HOME/yoi` | `$HOME/.config/yoi` |
//! | data | `INSOMNIA_DATA_DIR` | `$INSOMNIA_HOME` | — | `$HOME/.insomnia` | //! | data | `YOI_DATA_DIR` | `$YOI_HOME` | — | `$HOME/.yoi` |
//! | runtime | `INSOMNIA_RUNTIME_DIR` | `$INSOMNIA_HOME/run` | `$XDG_RUNTIME_DIR/insomnia` | `$HOME/.insomnia/run` | //! | runtime | `YOI_RUNTIME_DIR` | `$YOI_HOME/run` | `$XDG_RUNTIME_DIR/yoi` | `$HOME/.yoi/run` |
//! //!
//! `INSOMNIA_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、 //! `YOI_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、
//! runtime は `$X/run` に集約される。テストや sandbox 利用ではこれ一本 //! runtime は `$X/run` に集約される。テストや sandbox 利用ではこれ一本
//! で全部 tempdir に向けられる。 //! で全部 tempdir に向けられる。
//! //!
@ -29,8 +29,8 @@ use std::path::PathBuf;
/// `prompts/` などが置かれる。 /// `prompts/` などが置かれる。
pub fn config_dir() -> Option<PathBuf> { pub fn config_dir() -> Option<PathBuf> {
resolve_config_dir_from_parts( resolve_config_dir_from_parts(
env_path("INSOMNIA_CONFIG_DIR"), env_path("YOI_CONFIG_DIR"),
env_path("INSOMNIA_HOME"), env_path("YOI_HOME"),
env_path("XDG_CONFIG_HOME"), env_path("XDG_CONFIG_HOME"),
env_path("HOME"), env_path("HOME"),
) )
@ -40,8 +40,8 @@ pub fn config_dir() -> Option<PathBuf> {
/// 置き場。 /// 置き場。
pub fn data_dir() -> Option<PathBuf> { pub fn data_dir() -> Option<PathBuf> {
resolve_data_dir_from_parts( resolve_data_dir_from_parts(
env_path("INSOMNIA_DATA_DIR"), env_path("YOI_DATA_DIR"),
env_path("INSOMNIA_HOME"), env_path("YOI_HOME"),
env_path("HOME"), env_path("HOME"),
) )
} }
@ -50,8 +50,8 @@ pub fn data_dir() -> Option<PathBuf> {
/// `status.json` 等が置かれる。再起動で消えて構わない。 /// `status.json` 等が置かれる。再起動で消えて構わない。
pub fn runtime_dir() -> Option<PathBuf> { pub fn runtime_dir() -> Option<PathBuf> {
resolve_runtime_dir_from_parts( resolve_runtime_dir_from_parts(
env_path("INSOMNIA_RUNTIME_DIR"), env_path("YOI_RUNTIME_DIR"),
env_path("INSOMNIA_HOME"), env_path("YOI_HOME"),
env_path("XDG_RUNTIME_DIR"), env_path("XDG_RUNTIME_DIR"),
env_path("HOME"), env_path("HOME"),
) )
@ -111,53 +111,53 @@ pub fn pod_socket_path(pod_name: &str) -> Option<PathBuf> {
// ---- internals -------------------------------------------------------------- // ---- internals --------------------------------------------------------------
fn resolve_config_dir_from_parts( fn resolve_config_dir_from_parts(
insomnia_config_dir: Option<PathBuf>, yoi_config_dir: Option<PathBuf>,
insomnia_home: Option<PathBuf>, yoi_home: Option<PathBuf>,
xdg_config_home: Option<PathBuf>, xdg_config_home: Option<PathBuf>,
home: Option<PathBuf>, home: Option<PathBuf>,
) -> Option<PathBuf> { ) -> Option<PathBuf> {
if let Some(p) = insomnia_config_dir { if let Some(p) = yoi_config_dir {
return Some(p); return Some(p);
} }
if let Some(p) = insomnia_home { if let Some(p) = yoi_home {
return Some(p.join("config")); return Some(p.join("config"));
} }
if let Some(p) = xdg_config_home { if let Some(p) = xdg_config_home {
return Some(p.join("insomnia")); return Some(p.join("yoi"));
} }
Some(home?.join(".config").join("insomnia")) Some(home?.join(".config").join("yoi"))
} }
fn resolve_data_dir_from_parts( fn resolve_data_dir_from_parts(
insomnia_data_dir: Option<PathBuf>, yoi_data_dir: Option<PathBuf>,
insomnia_home: Option<PathBuf>, yoi_home: Option<PathBuf>,
home: Option<PathBuf>, home: Option<PathBuf>,
) -> Option<PathBuf> { ) -> Option<PathBuf> {
if let Some(p) = insomnia_data_dir { if let Some(p) = yoi_data_dir {
return Some(p); return Some(p);
} }
if let Some(p) = insomnia_home { if let Some(p) = yoi_home {
return Some(p); return Some(p);
} }
Some(home?.join(".insomnia")) Some(home?.join(".yoi"))
} }
fn resolve_runtime_dir_from_parts( fn resolve_runtime_dir_from_parts(
insomnia_runtime_dir: Option<PathBuf>, yoi_runtime_dir: Option<PathBuf>,
insomnia_home: Option<PathBuf>, yoi_home: Option<PathBuf>,
xdg_runtime_dir: Option<PathBuf>, xdg_runtime_dir: Option<PathBuf>,
home: Option<PathBuf>, home: Option<PathBuf>,
) -> Option<PathBuf> { ) -> Option<PathBuf> {
if let Some(p) = insomnia_runtime_dir { if let Some(p) = yoi_runtime_dir {
return Some(p); return Some(p);
} }
if let Some(p) = insomnia_home { if let Some(p) = yoi_home {
return Some(p.join("run")); return Some(p.join("run"));
} }
if let Some(p) = xdg_runtime_dir { if let Some(p) = xdg_runtime_dir {
return Some(p.join("insomnia")); return Some(p.join("yoi"));
} }
Some(home?.join(".insomnia").join("run")) Some(home?.join(".yoi").join("run"))
} }
fn user_profiles_path_from_config_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> { fn user_profiles_path_from_config_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
@ -221,7 +221,7 @@ mod tests {
fn config_dir_falls_back_to_home_dot_config() { fn config_dir_falls_back_to_home_dot_config() {
assert_eq!( assert_eq!(
resolve_config_dir_from_parts(None, None, None, Some(PathBuf::from("/h"))).unwrap(), resolve_config_dir_from_parts(None, None, None, Some(PathBuf::from("/h"))).unwrap(),
PathBuf::from("/h/.config/insomnia") PathBuf::from("/h/.config/yoi")
); );
} }
@ -235,12 +235,12 @@ mod tests {
Some(PathBuf::from("/h")), Some(PathBuf::from("/h")),
) )
.unwrap(), .unwrap(),
PathBuf::from("/x/insomnia") PathBuf::from("/x/yoi")
); );
} }
#[test] #[test]
fn config_dir_insomnia_home_outranks_xdg() { fn config_dir_yoi_home_outranks_xdg() {
assert_eq!( assert_eq!(
resolve_config_dir_from_parts( resolve_config_dir_from_parts(
None, None,
@ -254,7 +254,7 @@ mod tests {
} }
#[test] #[test]
fn config_dir_explicit_wins_over_insomnia_home() { fn config_dir_explicit_wins_over_yoi_home() {
assert_eq!( assert_eq!(
resolve_config_dir_from_parts( resolve_config_dir_from_parts(
Some(PathBuf::from("/explicit-cfg")), Some(PathBuf::from("/explicit-cfg")),
@ -268,15 +268,15 @@ mod tests {
} }
#[test] #[test]
fn data_dir_default_is_dot_insomnia() { fn data_dir_default_is_dot_yoi() {
assert_eq!( assert_eq!(
resolve_data_dir_from_parts(None, None, Some(PathBuf::from("/h"))).unwrap(), resolve_data_dir_from_parts(None, None, Some(PathBuf::from("/h"))).unwrap(),
PathBuf::from("/h/.insomnia") PathBuf::from("/h/.yoi")
); );
} }
#[test] #[test]
fn data_dir_insomnia_home_is_data_dir_itself() { fn data_dir_yoi_home_is_data_dir_itself() {
assert_eq!( assert_eq!(
resolve_data_dir_from_parts( resolve_data_dir_from_parts(
None, None,
@ -289,7 +289,7 @@ mod tests {
} }
#[test] #[test]
fn data_dir_explicit_wins_over_insomnia_home() { fn data_dir_explicit_wins_over_yoi_home() {
assert_eq!( assert_eq!(
resolve_data_dir_from_parts( resolve_data_dir_from_parts(
Some(PathBuf::from("/explicit-data")), Some(PathBuf::from("/explicit-data")),
@ -311,20 +311,20 @@ mod tests {
Some(PathBuf::from("/h")), Some(PathBuf::from("/h")),
) )
.unwrap(), .unwrap(),
PathBuf::from("/xdg-runtime/insomnia") PathBuf::from("/xdg-runtime/yoi")
); );
} }
#[test] #[test]
fn runtime_dir_falls_back_to_dot_insomnia_run() { fn runtime_dir_falls_back_to_dot_yoi_run() {
assert_eq!( assert_eq!(
resolve_runtime_dir_from_parts(None, None, None, Some(PathBuf::from("/h"))).unwrap(), resolve_runtime_dir_from_parts(None, None, None, Some(PathBuf::from("/h"))).unwrap(),
PathBuf::from("/h/.insomnia/run") PathBuf::from("/h/.yoi/run")
); );
} }
#[test] #[test]
fn runtime_dir_insomnia_home_is_run_subdir() { fn runtime_dir_yoi_home_is_run_subdir() {
assert_eq!( assert_eq!(
resolve_runtime_dir_from_parts( resolve_runtime_dir_from_parts(
None, None,
@ -338,7 +338,7 @@ mod tests {
} }
#[test] #[test]
fn runtime_dir_explicit_wins_over_insomnia_home() { fn runtime_dir_explicit_wins_over_yoi_home() {
assert_eq!( assert_eq!(
resolve_runtime_dir_from_parts( resolve_runtime_dir_from_parts(
Some(PathBuf::from("/explicit-run")), Some(PathBuf::from("/explicit-run")),
@ -358,7 +358,7 @@ mod tests {
assert_eq!( assert_eq!(
resolve_config_dir_from_parts(None, None, xdg_config_home, Some(PathBuf::from("/h"))) resolve_config_dir_from_parts(None, None, xdg_config_home, Some(PathBuf::from("/h")))
.unwrap(), .unwrap(),
PathBuf::from("/h/.config/insomnia") PathBuf::from("/h/.config/yoi")
); );
} }

View File

@ -20,11 +20,11 @@ use crate::{
ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifestConfig, paths, ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifestConfig, paths,
}; };
const PROFILE_FORMAT_V1: &str = "insomnia.lua-profile.v1"; const PROFILE_FORMAT_V1: &str = "yoi.lua-profile.v1";
const BUILTIN_DEFAULT_PROFILE_NAME: &str = "default"; const BUILTIN_DEFAULT_PROFILE_NAME: &str = "default";
const BUILTIN_DEFAULT_PROFILE: &str = include_str!("../../../resources/profiles/default.lua"); const BUILTIN_DEFAULT_PROFILE: &str = include_str!("../../../resources/profiles/default.lua");
const BUILTIN_MODEL_CATALOG: &str = include_str!("../../../resources/models/builtin.toml"); const BUILTIN_MODEL_CATALOG: &str = include_str!("../../../resources/models/builtin.toml");
const DEFAULT_POD_NAME: &str = "insomnia"; const DEFAULT_POD_NAME: &str = "yoi";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -694,7 +694,7 @@ fn find_project_profiles_from(start: &Path) -> Option<PathBuf> {
.unwrap_or_else(|| start.to_path_buf()); .unwrap_or_else(|| start.to_path_buf());
let mut cur: Option<&Path> = Some(start.as_path()); let mut cur: Option<&Path> = Some(start.as_path());
while let Some(dir) = cur { while let Some(dir) = cur {
let candidate = dir.join(".insomnia").join("profiles.toml"); let candidate = dir.join(".yoi").join("profiles.toml");
if candidate.is_file() { if candidate.is_file() {
return Some(candidate); return Some(candidate);
} }
@ -709,7 +709,7 @@ fn add_builtin_profiles(registry: &mut ProfileRegistry) {
BUILTIN_DEFAULT_PROFILE_NAME, BUILTIN_DEFAULT_PROFILE_NAME,
"builtin:default", "builtin:default",
BUILTIN_DEFAULT_PROFILE, BUILTIN_DEFAULT_PROFILE,
Some("Bundled default Insomnia coding profile".into()), Some("Bundled default Yoi coding profile".into()),
)); ));
} }
@ -827,7 +827,7 @@ fn require_module(
if let Some(value) = host_module(lua, name)? { if let Some(value) = host_module(lua, name)? {
return Ok(value); return Ok(value);
} }
if name.starts_with("insomnia.") || name == "insomnia" { if name.starts_with("yoi.") || name == "yoi" {
return Err(mlua::Error::RuntimeError(format!( return Err(mlua::Error::RuntimeError(format!(
"unknown host module `{name}`" "unknown host module `{name}`"
))); )));
@ -876,7 +876,7 @@ fn require_module(
fn host_module(lua: &Lua, name: &str) -> mlua::Result<Option<LuaValue>> { fn host_module(lua: &Lua, name: &str) -> mlua::Result<Option<LuaValue>> {
match name { match name {
"insomnia" => { "yoi" => {
let t = lua.create_table()?; let t = lua.create_table()?;
t.set("profile", profile_function(lua)?)?; t.set("profile", profile_function(lua)?)?;
t.set("models", models_module(lua)?)?; t.set("models", models_module(lua)?)?;
@ -884,10 +884,10 @@ fn host_module(lua: &Lua, name: &str) -> mlua::Result<Option<LuaValue>> {
t.set("scope", scope_module(lua)?)?; t.set("scope", scope_module(lua)?)?;
Ok(Some(LuaValue::Table(t))) Ok(Some(LuaValue::Table(t)))
} }
"insomnia.profile" => Ok(Some(LuaValue::Function(profile_function(lua)?))), "yoi.profile" => Ok(Some(LuaValue::Function(profile_function(lua)?))),
"insomnia.models" => Ok(Some(LuaValue::Table(models_module(lua)?))), "yoi.models" => Ok(Some(LuaValue::Table(models_module(lua)?))),
"insomnia.compact" => Ok(Some(LuaValue::Table(compact_module(lua)?))), "yoi.compact" => Ok(Some(LuaValue::Table(compact_module(lua)?))),
"insomnia.scope" => Ok(Some(LuaValue::Table(scope_module(lua)?))), "yoi.scope" => Ok(Some(LuaValue::Table(scope_module(lua)?))),
_ => Ok(None), _ => Ok(None),
} }
} }
@ -997,7 +997,7 @@ fn reject_manifest_shaped_profile(value: &serde_json::Value) -> Result<(), Profi
for key in ["allow", "deny"] { for key in ["allow", "deny"] {
if scope.contains_key(key) { if scope.contains_key(key) {
return Err(ProfileError::InvalidProfile(format!( return Err(ProfileError::InvalidProfile(format!(
"field `scope.{key}` grants concrete authority and is not allowed in reusable Profiles; use require(\"insomnia.scope\") intent helpers" "field `scope.{key}` grants concrete authority and is not allowed in reusable Profiles; use require(\"yoi.scope\") intent helpers"
))); )));
} }
} }
@ -1312,8 +1312,8 @@ mod tests {
tmp.path(), tmp.path(),
"coder.lua", "coder.lua",
r#" r#"
local profile = require("insomnia.profile") local profile = require("yoi.profile")
local scope = require("insomnia.scope") local scope = require("yoi.scope")
return profile { return profile {
slug = "coder", slug = "coder",
model = { scheme = "anthropic", model_id = "claude-sonnet-4-20250514" }, model = { scheme = "anthropic", model_id = "claude-sonnet-4-20250514" },
@ -1352,19 +1352,19 @@ return profile {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
std::fs::write( std::fs::write(
tmp.path().join("shared.lua"), tmp.path().join("shared.lua"),
r#"return { model = require("insomnia.models").catalog("codex-oauth/gpt-5.5") }"#, r#"return { model = require("yoi.models").catalog("codex-oauth/gpt-5.5") }"#,
) )
.unwrap(); .unwrap();
let profile = write_profile( let profile = write_profile(
tmp.path(), tmp.path(),
"main.lua", "main.lua",
r#" r#"
local insomnia = require("insomnia") local yoi = require("yoi")
local shared = require("shared") local shared = require("shared")
return insomnia.profile { return yoi.profile {
slug = "main", slug = "main",
model = shared.model, model = shared.model,
scope = insomnia.scope.workspace_write(), scope = yoi.scope.workspace_write(),
} }
"#, "#,
); );
@ -1445,9 +1445,9 @@ return insomnia.profile {
tmp.path(), tmp.path(),
"ratio.lua", "ratio.lua",
r#" r#"
local profile = require("insomnia.profile") local profile = require("yoi.profile")
local models = require("insomnia.models") local models = require("yoi.models")
local compact = require("insomnia.compact") local compact = require("yoi.compact")
return profile { return profile {
model = models.catalog("codex-oauth/gpt-5.5"), model = models.catalog("codex-oauth/gpt-5.5"),
compaction = compact.ratio { threshold = 0.5, request = 0.75, worker = 0.25 }, compaction = compact.ratio { threshold = 0.5, request = 0.75, worker = 0.25 },
@ -1473,7 +1473,7 @@ return profile {
.with_workspace_base(tmp.path()) .with_workspace_base(tmp.path())
.resolve(&ProfileSelector::Default, ProfileResolveOptions::default()) .resolve(&ProfileSelector::Default, ProfileResolveOptions::default())
.unwrap(); .unwrap();
assert_eq!(resolved.manifest.pod.name, "insomnia"); assert_eq!(resolved.manifest.pod.name, "yoi");
assert_eq!( assert_eq!(
resolved.manifest.model.ref_.as_deref(), resolved.manifest.model.ref_.as_deref(),
Some("codex-oauth/gpt-5.5") Some("codex-oauth/gpt-5.5")
@ -1516,7 +1516,7 @@ return profile {
fn discovery_reads_user_and_project_registry_and_project_default_wins() { fn discovery_reads_user_and_project_registry_and_project_default_wins() {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let user_config = tmp.path().join("profiles.toml"); let user_config = tmp.path().join("profiles.toml");
let project_dir = tmp.path().join("project/.insomnia"); let project_dir = tmp.path().join("project/.yoi");
std::fs::create_dir_all(&project_dir).unwrap(); std::fs::create_dir_all(&project_dir).unwrap();
let project_config = project_dir.join("profiles.toml"); let project_config = project_dir.join("profiles.toml");
std::fs::write( std::fs::write(
@ -1542,7 +1542,7 @@ return profile {
#[test] #[test]
fn default_marks_direct_profile_entry() { fn default_marks_direct_profile_entry() {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let project_dir = tmp.path().join("project/.insomnia"); let project_dir = tmp.path().join("project/.yoi");
std::fs::create_dir_all(&project_dir).unwrap(); std::fs::create_dir_all(&project_dir).unwrap();
let project_config = project_dir.join("profiles.toml"); let project_config = project_dir.join("profiles.toml");
std::fs::write( std::fs::write(

View File

@ -1,12 +1,12 @@
//! Append-only JSONL audit log for memory workers and tools. //! Append-only JSONL audit log for memory workers and tools.
//! //!
//! The log is evidence-only observability data under //! The log is evidence-only observability data under
//! `.insomnia/memory/_logs/current.log`. It is intentionally separate from //! `.yoi/memory/_logs/current.log`. It is intentionally separate from
//! `_staging` and `_usage`, and consolidation never consumes it. Operators can //! `_staging` and `_usage`, and consolidation never consumes it. Operators can
//! follow the latest stream with: //! follow the latest stream with:
//! //!
//! ```text //! ```text
//! tail -f .insomnia/memory/_logs/current.log //! tail -f .yoi/memory/_logs/current.log
//! ``` //! ```
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -260,7 +260,7 @@ pub struct RecordSnapshot {
pub hash: String, pub hash: String,
} }
/// Append one audit event to `.insomnia/memory/_logs/current.log`. /// Append one audit event to `.yoi/memory/_logs/current.log`.
pub fn append_audit_event(layout: &WorkspaceLayout, event: &AuditEvent) -> io::Result<()> { pub fn append_audit_event(layout: &WorkspaceLayout, event: &AuditEvent) -> io::Result<()> {
let path = layout.audit_current_log_path(); let path = layout.audit_current_log_path();
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
@ -425,7 +425,7 @@ mod tests {
#[test] #[test]
fn counts_created_edited_deleted_records() { fn counts_created_edited_deleted_records() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let decision_dir = dir.path().join(".insomnia/memory/decisions"); let decision_dir = dir.path().join(".yoi/memory/decisions");
fs::create_dir_all(&decision_dir).unwrap(); fs::create_dir_all(&decision_dir).unwrap();
fs::write(decision_dir.join("a.md"), "old").unwrap(); fs::write(decision_dir.join("a.md"), "old").unwrap();
fs::write(decision_dir.join("gone.md"), "old").unwrap(); fs::write(decision_dir.join("gone.md"), "old").unwrap();

View File

@ -74,7 +74,7 @@ pub fn render_staging_records(entries: &[StagingEntry]) -> String {
out out
} }
/// `<workspace>/.insomnia/memory/{summary.md,decisions/*,requests/*}` を /// `<workspace>/.yoi/memory/{summary.md,decisions/*,requests/*}` を
/// 「`### <kind>:<slug>` ヘッダ + raw markdown ブロック」で全文渡す。 /// 「`### <kind>:<slug>` ヘッダ + raw markdown ブロック」で全文渡す。
pub fn render_existing_memory_records(layout: &WorkspaceLayout) -> String { pub fn render_existing_memory_records(layout: &WorkspaceLayout) -> String {
let mut out = String::new(); let mut out = String::new();
@ -230,11 +230,11 @@ mod tests {
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
write( write(
&dir.path().join(".insomnia/memory/summary.md"), &dir.path().join(".yoi/memory/summary.md"),
&format!("---\nupdated_at: {n}\n---\nstate of the world\n", n = now()), &format!("---\nupdated_at: {n}\n---\nstate of the world\n", n = now()),
); );
write( write(
&dir.path().join(".insomnia/memory/decisions/dec.md"), &dir.path().join(".yoi/memory/decisions/dec.md"),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n",
n = now() n = now()

View File

@ -9,7 +9,7 @@
//! が追加した分は残す //! が追加した分は残す
//! //!
//! 占有判定は Linux/macOS の `kill(pid, 0)` 経由で行う(`ESRCH` で死亡判定)。 //! 占有判定は Linux/macOS の `kill(pid, 0)` 経由で行う(`ESRCH` で死亡判定)。
//! Windows は対象外: INSOMNIA は POSIX 環境を前提にしている。 //! Windows は対象外: Yoi は POSIX 環境を前提にしている。
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};

View File

@ -133,8 +133,8 @@ pub fn collect_tidy_hints(layout: &WorkspaceLayout) -> TidyHints {
hints hints
} }
/// `<root>/.insomnia/memory/<kind>/*.md` (Knowledge は /// `<root>/.yoi/memory/<kind>/*.md` (Knowledge は
/// `<root>/.insomnia/knowledge/*.md`) を slug ごとに `(slug, full content)` /// `<root>/.yoi/knowledge/*.md`) を slug ごとに `(slug, full content)`
/// 化して返す。 /// 化して返す。
fn read_kind_records(layout: &WorkspaceLayout, kind: RecordKind) -> BTreeMap<String, String> { fn read_kind_records(layout: &WorkspaceLayout, kind: RecordKind) -> BTreeMap<String, String> {
let dir = match kind { let dir = match kind {
@ -280,14 +280,14 @@ mod tests {
fn collects_replaced_chain() { fn collects_replaced_chain() {
let (dir, layout) = workspace(); let (dir, layout) = workspace();
write( write(
&dir.path().join(".insomnia/memory/decisions/replaced.md"), &dir.path().join(".yoi/memory/decisions/replaced.md"),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: replaced\nreplaced_by: winner\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: replaced\nreplaced_by: winner\n---\n",
n = now() n = now()
), ),
); );
write( write(
&dir.path().join(".insomnia/memory/decisions/winner.md"), &dir.path().join(".yoi/memory/decisions/winner.md"),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = now() n = now()
@ -308,7 +308,7 @@ mod tests {
.map(|i| format!(" - segment_id: s{i}\n range: [{i}, {i}]\n")) .map(|i| format!(" - segment_id: s{i}\n range: [{i}, {i}]\n"))
.collect(); .collect();
write( write(
&dir.path().join(".insomnia/memory/decisions/big.md"), &dir.path().join(".yoi/memory/decisions/big.md"),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nstatus: open\nsources:\n{m}---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nstatus: open\nsources:\n{m}---\n",
n = now(), n = now(),
@ -327,8 +327,7 @@ mod tests {
let (dir, layout) = workspace(); let (dir, layout) = workspace();
for slug in ["db-pool", "db-pol", "db-pools", "alpha"] { for slug in ["db-pool", "db-pol", "db-pools", "alpha"] {
write( write(
&dir.path() &dir.path().join(format!(".yoi/memory/decisions/{slug}.md")),
.join(format!(".insomnia/memory/decisions/{slug}.md")),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = now() n = now()

View File

@ -1,7 +1,7 @@
//! extract: 活動抽出。 //! extract: 活動抽出。
//! //!
//! 通常 Pod の post-run hook で発火する disposable Worker と、その //! 通常 Pod の post-run hook で発火する disposable Worker と、その
//! 出力を `<workspace>/.insomnia/memory/_staging/<id>.json` に書き出す //! 出力を `<workspace>/.yoi/memory/_staging/<id>.json` に書き出す
//! ヘルパーを提供する。Pod 側はこのモジュールから: //! ヘルパーを提供する。Pod 側はこのモジュールから:
//! //!
//! - [`build_extract_input`] を sub-Worker の最初の user 入力に //! - [`build_extract_input`] を sub-Worker の最初の user 入力に

View File

@ -1,4 +1,4 @@
//! `<workspace>/.insomnia/memory/_staging/<id>.json` への書き出しヘルパー。 //! `<workspace>/.yoi/memory/_staging/<id>.json` への書き出しヘルパー。
//! //!
//! 1 件 1 ファイル、UUIDv7 命名(短命なので衝突回避と順序を兼ねる)。 //! 1 件 1 ファイル、UUIDv7 命名(短命なので衝突回避と順序を兼ねる)。
//! `source` を機械付与した [`StagingRecord`] 形式で保存する。 //! `source` を機械付与した [`StagingRecord`] 形式で保存する。

View File

@ -291,7 +291,7 @@ mod tests {
#[test] #[test]
fn decision_with_unknown_replaced_by_errors() { fn decision_with_unknown_replaced_by_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: ghost\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: ghost\n---\nbody\n",
now = iso_now() now = iso_now()
@ -308,7 +308,7 @@ mod tests {
#[test] #[test]
fn decision_replaced_by_self_errors() { fn decision_replaced_by_self_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: foo\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: foo\n---\nbody\n",
now = iso_now() now = iso_now()
@ -326,7 +326,7 @@ mod tests {
fn decision_replaced_by_existing_ok() { fn decision_replaced_by_existing_ok() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
// Pre-create the target. // Pre-create the target.
let target = dir.path().join(".insomnia/memory/decisions/bar.md"); let target = dir.path().join(".yoi/memory/decisions/bar.md");
write( write(
&target, &target,
&format!( &format!(
@ -334,7 +334,7 @@ mod tests {
now = iso_now() now = iso_now()
), ),
); );
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: bar\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: bar\n---\nbody\n",
now = iso_now() now = iso_now()
@ -346,7 +346,7 @@ mod tests {
#[test] #[test]
fn missing_required_field_errors() { fn missing_required_field_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
// Missing `status`. // Missing `status`.
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\n---\nbody\n",
@ -363,7 +363,7 @@ mod tests {
#[test] #[test]
fn knowledge_long_description_with_model_invokation_errors() { fn knowledge_long_description_with_model_invokation_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/knowledge/foo.md"); let path = dir.path().join(".yoi/knowledge/foo.md");
let big_desc = "x".repeat(2000); let big_desc = "x".repeat(2000);
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: true\nuser_invocable: true\nlast_sources: []\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: true\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
@ -381,7 +381,7 @@ mod tests {
#[test] #[test]
fn knowledge_long_description_without_model_invokation_ok() { fn knowledge_long_description_without_model_invokation_ok() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/knowledge/foo.md"); let path = dir.path().join(".yoi/knowledge/foo.md");
let big_desc = "x".repeat(2000); let big_desc = "x".repeat(2000);
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
@ -394,7 +394,7 @@ mod tests {
#[test] #[test]
fn summary_path_accepted() { fn summary_path_accepted() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/memory/summary.md"); let path = dir.path().join(".yoi/memory/summary.md");
let content = format!( let content = format!(
"---\nupdated_at: {now}\n---\nsummary body\n", "---\nupdated_at: {now}\n---\nsummary body\n",
now = iso_now() now = iso_now()
@ -406,7 +406,7 @@ mod tests {
#[test] #[test]
fn create_when_existing_errors() { fn create_when_existing_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
write( write(
&path, &path,
&format!( &format!(
@ -434,15 +434,14 @@ mod tests {
// `db-pol` (1 deletion), `db-pools` (1 insertion). // `db-pol` (1 deletion), `db-pools` (1 insertion).
for slug in ["db-pol", "db-pools"] { for slug in ["db-pol", "db-pools"] {
write( write(
&dir.path() &dir.path().join(format!(".yoi/memory/decisions/{slug}.md")),
.join(format!(".insomnia/memory/decisions/{slug}.md")),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = iso_now() n = iso_now()
), ),
); );
} }
let path = dir.path().join(".insomnia/memory/decisions/db-pool.md"); let path = dir.path().join(".yoi/memory/decisions/db-pool.md");
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n",
n = iso_now() n = iso_now()
@ -464,15 +463,14 @@ mod tests {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
for slug in ["alpha", "bravo"] { for slug in ["alpha", "bravo"] {
write( write(
&dir.path() &dir.path().join(format!(".yoi/memory/decisions/{slug}.md")),
.join(format!(".insomnia/memory/decisions/{slug}.md")),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = iso_now() n = iso_now()
), ),
); );
} }
let path = dir.path().join(".insomnia/memory/decisions/charlie.md"); let path = dir.path().join(".yoi/memory/decisions/charlie.md");
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = iso_now() n = iso_now()
@ -491,7 +489,7 @@ mod tests {
#[test] #[test]
fn body_size_limit_errors() { fn body_size_limit_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
let big_body = "x".repeat(8001); let big_body = "x".repeat(8001);
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: open\n---\n{body}", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: open\n---\n{body}",

View File

@ -5,7 +5,7 @@
//! - [`collect_resident_knowledge`] — resident-injection candidates //! - [`collect_resident_knowledge`] — resident-injection candidates
//! (`model_invokation: true`) returned as `(slug, description)` pairs. //! (`model_invokation: true`) returned as `(slug, description)` pairs.
//! - [`collect_resident_summary`] — the body of //! - [`collect_resident_summary`] — the body of
//! `<workspace>/.insomnia/memory/summary.md` when it parses as a summary //! `<workspace>/.yoi/memory/summary.md` when it parses as a summary
//! record and has non-empty body. //! record and has non-empty body.
//! - [`list_knowledge_slugs`] — every slug whose file parses, regardless //! - [`list_knowledge_slugs`] — every slug whose file parses, regardless
//! of `model_invokation`. Used by the Pod IPC layer to answer TUI `#` //! of `model_invokation`. Used by the Pod IPC layer to answer TUI `#`
@ -25,7 +25,7 @@ pub struct ResidentKnowledgeEntry {
pub description: String, pub description: String,
} }
/// Walk `<workspace>/.insomnia/knowledge/*.md` and return entries whose /// Walk `<workspace>/.yoi/knowledge/*.md` and return entries whose
/// frontmatter has `model_invokation: true`, sorted by slug. A missing /// frontmatter has `model_invokation: true`, sorted by slug. A missing
/// directory yields an empty vec. /// directory yields an empty vec.
pub fn collect_resident_knowledge(layout: &WorkspaceLayout) -> Vec<ResidentKnowledgeEntry> { pub fn collect_resident_knowledge(layout: &WorkspaceLayout) -> Vec<ResidentKnowledgeEntry> {
@ -42,7 +42,7 @@ pub fn collect_resident_knowledge(layout: &WorkspaceLayout) -> Vec<ResidentKnowl
out out
} }
/// Read `<workspace>/.insomnia/memory/summary.md` for resident prompt /// Read `<workspace>/.yoi/memory/summary.md` for resident prompt
/// injection. Returns only the markdown body (frontmatter stripped), and /// injection. Returns only the markdown body (frontmatter stripped), and
/// degrades to `None` for missing, unreadable, malformed, or empty records. /// degrades to `None` for missing, unreadable, malformed, or empty records.
pub fn collect_resident_summary(layout: &WorkspaceLayout) -> Option<String> { pub fn collect_resident_summary(layout: &WorkspaceLayout) -> Option<String> {
@ -115,7 +115,7 @@ mod tests {
} }
fn write_summary(dir: &Path, body: &str) { fn write_summary(dir: &Path, body: &str) {
let path = dir.join(".insomnia/memory/summary.md"); let path = dir.join(".yoi/memory/summary.md");
let content = format!("---\nupdated_at: {n}\n---\n{body}", n = now()); let content = format!("---\nupdated_at: {n}\n---\n{body}", n = now());
std::fs::write(path, content).unwrap(); std::fs::write(path, content).unwrap();
} }
@ -127,7 +127,7 @@ mod tests {
model_invokation: bool, model_invokation: bool,
body: &str, body: &str,
) { ) {
let path = dir.join(".insomnia/knowledge").join(format!("{slug}.md")); let path = dir.join(".yoi/knowledge").join(format!("{slug}.md"));
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nkind: policy\ndescription: \"{description}\"\nmodel_invokation: {flag}\nuser_invocable: true\nlast_sources: []\n---\n{body}", "---\ncreated_at: {n}\nupdated_at: {n}\nkind: policy\ndescription: \"{description}\"\nmodel_invokation: {flag}\nuser_invocable: true\nlast_sources: []\n---\n{body}",
n = now(), n = now(),
@ -138,8 +138,8 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout) { fn setup() -> (TempDir, WorkspaceLayout) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/knowledge")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/knowledge")).unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/memory")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/memory")).unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
(dir, layout) (dir, layout)
} }
@ -166,7 +166,7 @@ mod tests {
fn malformed_summary_returns_none() { fn malformed_summary_returns_none() {
let (dir, layout) = setup(); let (dir, layout) = setup();
std::fs::write( std::fs::write(
dir.path().join(".insomnia/memory/summary.md"), dir.path().join(".yoi/memory/summary.md"),
"---\nthis is not yaml: : :\n---\nbody\n", "---\nthis is not yaml: : :\n---\nbody\n",
) )
.unwrap(); .unwrap();
@ -222,7 +222,7 @@ mod tests {
write_knowledge(dir.path(), "good", "ok", true, ""); write_knowledge(dir.path(), "good", "ok", true, "");
// Garbage in frontmatter — must be skipped, not panic. // Garbage in frontmatter — must be skipped, not panic.
std::fs::write( std::fs::write(
dir.path().join(".insomnia/knowledge/bad.md"), dir.path().join(".yoi/knowledge/bad.md"),
"---\nthis is not yaml: : :\n---\nbody\n", "---\nthis is not yaml: : :\n---\nbody\n",
) )
.unwrap(); .unwrap();
@ -236,11 +236,7 @@ mod tests {
fn non_md_files_ignored() { fn non_md_files_ignored() {
let (dir, layout) = setup(); let (dir, layout) = setup();
write_knowledge(dir.path(), "good", "ok", true, ""); write_knowledge(dir.path(), "good", "ok", true, "");
std::fs::write( std::fs::write(dir.path().join(".yoi/knowledge/note.txt"), "not markdown\n").unwrap();
dir.path().join(".insomnia/knowledge/note.txt"),
"not markdown\n",
)
.unwrap();
let got = collect_resident_knowledge(&layout); let got = collect_resident_knowledge(&layout);
assert_eq!(got.len(), 1); assert_eq!(got.len(), 1);
@ -269,15 +265,11 @@ mod tests {
let (dir, layout) = setup(); let (dir, layout) = setup();
write_knowledge(dir.path(), "good", "ok", true, ""); write_knowledge(dir.path(), "good", "ok", true, "");
std::fs::write( std::fs::write(
dir.path().join(".insomnia/knowledge/bad.md"), dir.path().join(".yoi/knowledge/bad.md"),
"---\nthis is not yaml: : :\n---\nbody\n", "---\nthis is not yaml: : :\n---\nbody\n",
) )
.unwrap(); .unwrap();
std::fs::write( std::fs::write(dir.path().join(".yoi/knowledge/note.txt"), "not markdown\n").unwrap();
dir.path().join(".insomnia/knowledge/note.txt"),
"not markdown\n",
)
.unwrap();
let got = list_knowledge_slugs(&layout); let got = list_knowledge_slugs(&layout);
assert_eq!(got, vec!["good"]); assert_eq!(got, vec!["good"]);

View File

@ -41,9 +41,9 @@ mod tests {
let layout = WorkspaceLayout::new(PathBuf::from("/ws")); let layout = WorkspaceLayout::new(PathBuf::from("/ws"));
let rules = deny_write_rules(&layout); let rules = deny_write_rules(&layout);
assert_eq!(rules.len(), 2); assert_eq!(rules.len(), 2);
assert_eq!(rules[0].target, PathBuf::from("/ws/.insomnia/memory")); assert_eq!(rules[0].target, PathBuf::from("/ws/.yoi/memory"));
assert_eq!(rules[0].permission, Permission::Write); assert_eq!(rules[0].permission, Permission::Write);
assert!(rules[0].recursive); assert!(rules[0].recursive);
assert_eq!(rules[1].target, PathBuf::from("/ws/.insomnia/knowledge")); assert_eq!(rules[1].target, PathBuf::from("/ws/.yoi/knowledge"));
} }
} }

View File

@ -294,7 +294,7 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout, PathBuf) { fn setup() -> (TempDir, WorkspaceLayout, PathBuf) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let initial = format!( let initial = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody body\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody body\n",

View File

@ -6,11 +6,11 @@
//! omitted, returns one entry per file (no excerpt) so the agent can //! omitted, returns one entry per file (no excerpt) so the agent can
//! enumerate what records exist without knowing what's inside them. //! enumerate what records exist without knowing what's inside them.
//! //!
//! - `MemoryQuery` walks `.insomnia/memory/{summary.md,decisions/, //! - `MemoryQuery` walks `.yoi/memory/{summary.md,decisions/,
//! requests/}`. `.insomnia/workflow/`, `.insomnia/memory/_staging/`, //! requests/}`. `.yoi/workflow/`, `.yoi/memory/_staging/`,
//! `.insomnia/memory/_usage/`, and `.insomnia/memory/_logs/` are excluded //! `.yoi/memory/_usage/`, and `.yoi/memory/_logs/` are excluded
//! by construction. //! by construction.
//! - `KnowledgeQuery` walks `.insomnia/knowledge/*.md` and supports a //! - `KnowledgeQuery` walks `.yoi/knowledge/*.md` and supports a
//! `kind` filter against the Knowledge frontmatter's `kind` field. //! `kind` filter against the Knowledge frontmatter's `kind` field.
//! //!
//! No derived index — the file tree is the source of truth and is //! No derived index — the file tree is the source of truth and is
@ -513,18 +513,16 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout) { fn setup() -> (TempDir, WorkspaceLayout) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
std::fs::create_dir_all(dir.path().join(".insomnia/memory/decisions")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/memory/decisions")).unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/memory/requests")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/memory/requests")).unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/memory/_staging")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/memory/_staging")).unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/workflow")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/workflow")).unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/knowledge")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/knowledge")).unwrap();
(dir, layout) (dir, layout)
} }
fn write_decision(dir: &Path, slug: &str, body: &str) { fn write_decision(dir: &Path, slug: &str, body: &str) {
let path = dir let path = dir.join(".yoi/memory/decisions").join(format!("{slug}.md"));
.join(".insomnia/memory/decisions")
.join(format!("{slug}.md"));
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n{body}", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n{body}",
n = now() n = now()
@ -533,7 +531,7 @@ mod tests {
} }
fn write_knowledge(dir: &Path, slug: &str, kind: &str, description: &str, body: &str) { fn write_knowledge(dir: &Path, slug: &str, kind: &str, description: &str, body: &str) {
let path = dir.join(".insomnia/knowledge").join(format!("{slug}.md")); let path = dir.join(".yoi/knowledge").join(format!("{slug}.md"));
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nkind: {kind}\ndescription: \"{description}\"\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\n{body}", "---\ncreated_at: {n}\nupdated_at: {n}\nkind: {kind}\ndescription: \"{description}\"\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\n{body}",
n = now() n = now()
@ -590,7 +588,7 @@ mod tests {
let (dir, layout) = setup(); let (dir, layout) = setup();
write_decision(dir.path(), "alpha", "body\n"); write_decision(dir.path(), "alpha", "body\n");
write_decision(dir.path(), "beta", "body\n"); write_decision(dir.path(), "beta", "body\n");
let summary_path = dir.path().join(".insomnia/memory/summary.md"); let summary_path = dir.path().join(".yoi/memory/summary.md");
std::fs::write( std::fs::write(
&summary_path, &summary_path,
format!("---\nupdated_at: {n}\n---\nhello\n", n = now()), format!("---\nupdated_at: {n}\n---\nhello\n", n = now()),
@ -610,7 +608,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn memory_query_finds_summary() { async fn memory_query_finds_summary() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let summary_path = dir.path().join(".insomnia/memory/summary.md"); let summary_path = dir.path().join(".yoi/memory/summary.md");
std::fs::write( std::fs::write(
&summary_path, &summary_path,
format!("---\nupdated_at: {n}\n---\nthe needle is here\n", n = now()), format!("---\nupdated_at: {n}\n---\nthe needle is here\n", n = now()),
@ -628,9 +626,9 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn memory_query_excludes_workflow_and_staging() { async fn memory_query_excludes_workflow_and_staging() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let wf = dir.path().join(".insomnia/workflow/wf.md"); let wf = dir.path().join(".yoi/workflow/wf.md");
std::fs::write(&wf, "needle in workflow\n").unwrap(); std::fs::write(&wf, "needle in workflow\n").unwrap();
let stg = dir.path().join(".insomnia/memory/_staging/abc.json"); let stg = dir.path().join(".yoi/memory/_staging/abc.json");
std::fs::write(&stg, "needle in staging\n").unwrap(); std::fs::write(&stg, "needle in staging\n").unwrap();
let (_, tool) = memory_query_tool(layout, QueryConfig::default())(); let (_, tool) = memory_query_tool(layout, QueryConfig::default())();

View File

@ -219,7 +219,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn read_decision_by_slug() { async fn read_decision_by_slug() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "alpha\nbeta\n").unwrap(); std::fs::write(&path, "alpha\nbeta\n").unwrap();
@ -234,7 +234,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn read_summary_without_slug() { async fn read_summary_without_slug() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/memory/summary.md"); let path = dir.path().join(".yoi/memory/summary.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "summary body\n").unwrap(); std::fs::write(&path, "summary body\n").unwrap();
@ -274,7 +274,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn knowledge_path_resolution() { async fn knowledge_path_resolution() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/knowledge/policy.md"); let path = dir.path().join(".yoi/knowledge/policy.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "k\n").unwrap(); std::fs::write(&path, "k\n").unwrap();
@ -287,7 +287,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn read_logs_explicit_use_when_usage_session_is_set() { async fn read_logs_explicit_use_when_usage_session_is_set() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "alpha\n").unwrap(); std::fs::write(&path, "alpha\n").unwrap();

View File

@ -219,7 +219,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn write_creates_summary() { async fn write_creates_summary() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/memory/summary.md"); let path = dir.path().join(".yoi/memory/summary.md");
let content = format!("---\nupdated_at: {n}\n---\nbody\n", n = now()); let content = format!("---\nupdated_at: {n}\n---\nbody\n", n = now());
let (meta, tool) = write_tool(layout)(); let (meta, tool) = write_tool(layout)();
@ -257,7 +257,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn write_update_existing() { async fn write_update_existing() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let initial = format!( let initial = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nold\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nold\n",
@ -290,7 +290,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn write_does_not_persist_on_lint_failure() { async fn write_does_not_persist_on_lint_failure() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join(".insomnia/memory/decisions/foo.md"); let path = dir.path().join(".yoi/memory/decisions/foo.md");
let bad = "no frontmatter at all"; let bad = "no frontmatter at all";
let (_, tool) = write_tool(layout)(); let (_, tool) = write_tool(layout)();
let inp = serde_json::json!({ let inp = serde_json::json!({

View File

@ -1,6 +1,6 @@
//! Workspace-local usage event log for memory / knowledge / workflow records. //! Workspace-local usage event log for memory / knowledge / workflow records.
//! //!
//! The log is append-only JSONL under the workspace's `.insomnia/` tree. It is //! The log is append-only JSONL under the workspace's `.yoi/` tree. It is
//! intentionally evidence-only: aggregation reports explicit context reads and //! intentionally evidence-only: aggregation reports explicit context reads and
//! resident exposure cost telemetry, but it does not classify records as //! resident exposure cost telemetry, but it does not classify records as
//! Knowledge candidates or tidy-protected records. //! Knowledge candidates or tidy-protected records.

View File

@ -1,25 +1,25 @@
//! Workspace-level path layout for the memory subsystem. //! Workspace-level path layout for the memory subsystem.
//! //!
//! `WorkspaceLayout` carries the workspace root (typically the Pod's //! `WorkspaceLayout` carries the workspace root (typically the Pod's
//! pwd). All insomnia-managed content lives under the conventional //! pwd). All yoi-managed content lives under the conventional
//! `<root>/.insomnia/` subdirectory — the same place that holds //! `<root>/.yoi/` subdirectory — the same place that holds
//! `profiles.toml`, `prompts/`, workflow, knowledge, and generated //! `profiles.toml`, `prompts/`, workflow, knowledge, and generated
//! memory. The trees inside it: //! memory. The trees inside it:
//! //!
//! - `<root>/.insomnia/workflow/<slug>.md` //! - `<root>/.yoi/workflow/<slug>.md`
//! - `<root>/.insomnia/knowledge/<slug>.md` //! - `<root>/.yoi/knowledge/<slug>.md`
//! - `<root>/.insomnia/memory/summary.md` //! - `<root>/.yoi/memory/summary.md`
//! - `<root>/.insomnia/memory/decisions/<slug>.md` //! - `<root>/.yoi/memory/decisions/<slug>.md`
//! - `<root>/.insomnia/memory/requests/<slug>.md` //! - `<root>/.yoi/memory/requests/<slug>.md`
//! - `<root>/.insomnia/memory/_staging/<id>.json` //! - `<root>/.yoi/memory/_staging/<id>.json`
//! - `<root>/.insomnia/memory/_logs/current.log` (append-only audit log) //! - `<root>/.yoi/memory/_logs/current.log` (append-only audit log)
//! //!
//! `memory/` is reserved for session-derived / generated state; //! `memory/` is reserved for session-derived / generated state;
//! Workflows are human-managed and live one level up under //! Workflows are human-managed and live one level up under
//! `.insomnia/workflow/`. //! `.yoi/workflow/`.
//! //!
//! Configuring `[memory]` with an empty body is therefore sufficient //! Configuring `[memory]` with an empty body is therefore sufficient
//! for any workspace that already uses the `.insomnia/` convention; no //! for any workspace that already uses the `.yoi/` convention; no
//! `workspace_root` override is needed. //! `workspace_root` override is needed.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -29,7 +29,7 @@ use crate::error::LintError;
#[cfg(test)] #[cfg(test)]
use lint_common::RecordLintError; use lint_common::RecordLintError;
const INSOMNIA_DIR: &str = ".insomnia"; const YOI_DIR: &str = ".yoi";
const MEMORY_DIR: &str = "memory"; const MEMORY_DIR: &str = "memory";
const KNOWLEDGE_DIR: &str = "knowledge"; const KNOWLEDGE_DIR: &str = "knowledge";
const WORKFLOW_DIR: &str = "workflow"; const WORKFLOW_DIR: &str = "workflow";
@ -100,17 +100,17 @@ impl WorkspaceLayout {
&self.root &self.root
} }
/// `<root>/.insomnia/`. The base of every other memory path. /// `<root>/.yoi/`. The base of every other memory path.
pub fn insomnia_dir(&self) -> PathBuf { pub fn yoi_dir(&self) -> PathBuf {
self.root.join(INSOMNIA_DIR) self.root.join(YOI_DIR)
} }
pub fn memory_dir(&self) -> PathBuf { pub fn memory_dir(&self) -> PathBuf {
self.insomnia_dir().join(MEMORY_DIR) self.yoi_dir().join(MEMORY_DIR)
} }
pub fn knowledge_dir(&self) -> PathBuf { pub fn knowledge_dir(&self) -> PathBuf {
self.insomnia_dir().join(KNOWLEDGE_DIR) self.yoi_dir().join(KNOWLEDGE_DIR)
} }
pub fn summary_path(&self) -> PathBuf { pub fn summary_path(&self) -> PathBuf {
@ -125,9 +125,9 @@ impl WorkspaceLayout {
self.memory_dir().join(REQUESTS_DIR) self.memory_dir().join(REQUESTS_DIR)
} }
/// Workflow directory: `<root>/.insomnia/workflow/`. /// Workflow directory: `<root>/.yoi/workflow/`.
pub fn workflow_dir(&self) -> PathBuf { pub fn workflow_dir(&self) -> PathBuf {
self.insomnia_dir().join(WORKFLOW_DIR) self.yoi_dir().join(WORKFLOW_DIR)
} }
pub fn staging_dir(&self) -> PathBuf { pub fn staging_dir(&self) -> PathBuf {
@ -149,7 +149,7 @@ impl WorkspaceLayout {
/// Tail-friendly latest memory audit log path. /// Tail-friendly latest memory audit log path.
/// ///
/// Operators can inspect live memory worker and tool events with: /// Operators can inspect live memory worker and tool events with:
/// `tail -f .insomnia/memory/_logs/current.log`. /// `tail -f .yoi/memory/_logs/current.log`.
pub fn audit_current_log_path(&self) -> PathBuf { pub fn audit_current_log_path(&self) -> PathBuf {
self.audit_logs_dir().join(AUDIT_CURRENT_LOG_FILE) self.audit_logs_dir().join(AUDIT_CURRENT_LOG_FILE)
} }
@ -171,12 +171,12 @@ impl WorkspaceLayout {
} }
/// Classify a path under the memory tree. Returns `None` if the /// Classify a path under the memory tree. Returns `None` if the
/// path is not under `.insomnia/memory/` or `.insomnia/knowledge/` /// path is not under `.yoi/memory/` or `.yoi/knowledge/`
/// of this workspace, or if it lives in /// of this workspace, or if it lives in
/// `_staging/` / `_usage/` / `_logs/` (opaque subsystem-owned trees). /// `_staging/` / `_usage/` / `_logs/` (opaque subsystem-owned trees).
/// ///
/// On a conventional path that's *almost* a record but malformed /// On a conventional path that's *almost* a record but malformed
/// (e.g. `.insomnia/memory/decisions/Foo.md` with an invalid slug), /// (e.g. `.yoi/memory/decisions/Foo.md` with an invalid slug),
/// returns `Err(LintError::Record(InvalidSlug) | InvalidPath)` so the caller /// returns `Err(LintError::Record(InvalidSlug) | InvalidPath)` so the caller
/// can surface it as a write violation. /// can surface it as a write violation.
pub fn classify(&self, path: &Path) -> Result<Option<ClassifiedPath>, LintError> { pub fn classify(&self, path: &Path) -> Result<Option<ClassifiedPath>, LintError> {
@ -265,7 +265,7 @@ mod tests {
#[test] #[test]
fn classifies_summary() { fn classifies_summary() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/summary.md")) .classify(&PathBuf::from("/ws/.yoi/memory/summary.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Summary); assert_eq!(cp.kind, RecordKind::Summary);
@ -275,7 +275,7 @@ mod tests {
#[test] #[test]
fn classifies_decision_with_slug() { fn classifies_decision_with_slug() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/decisions/foo-bar.md")) .classify(&PathBuf::from("/ws/.yoi/memory/decisions/foo-bar.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Decision); assert_eq!(cp.kind, RecordKind::Decision);
@ -285,7 +285,7 @@ mod tests {
#[test] #[test]
fn classifies_knowledge() { fn classifies_knowledge() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/.insomnia/knowledge/x.md")) .classify(&PathBuf::from("/ws/.yoi/knowledge/x.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Knowledge); assert_eq!(cp.kind, RecordKind::Knowledge);
@ -294,7 +294,7 @@ mod tests {
#[test] #[test]
fn workflow_under_memory_is_invalid_path() { fn workflow_under_memory_is_invalid_path() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/workflow/wf.md")) .classify(&PathBuf::from("/ws/.yoi/memory/workflow/wf.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, LintError::InvalidPath(_))); assert!(matches!(err, LintError::InvalidPath(_)));
} }
@ -303,7 +303,7 @@ mod tests {
fn staging_returns_none() { fn staging_returns_none() {
assert!( assert!(
layout() layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/_staging/abc.json")) .classify(&PathBuf::from("/ws/.yoi/memory/_staging/abc.json"))
.unwrap() .unwrap()
.is_none() .is_none()
); );
@ -312,7 +312,7 @@ mod tests {
#[test] #[test]
fn usage_tree_is_opaque_to_classifier() { fn usage_tree_is_opaque_to_classifier() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/_usage/events.jsonl")) .classify(&PathBuf::from("/ws/.yoi/memory/_usage/events.jsonl"))
.unwrap(); .unwrap();
assert!(cp.is_none()); assert!(cp.is_none());
} }
@ -320,7 +320,7 @@ mod tests {
#[test] #[test]
fn logs_tree_is_opaque_to_classifier() { fn logs_tree_is_opaque_to_classifier() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/_logs/current.log")) .classify(&PathBuf::from("/ws/.yoi/memory/_logs/current.log"))
.unwrap(); .unwrap();
assert!(cp.is_none()); assert!(cp.is_none());
} }
@ -344,7 +344,7 @@ mod tests {
#[test] #[test]
fn invalid_slug_rejected() { fn invalid_slug_rejected() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/decisions/Foo.md")) .classify(&PathBuf::from("/ws/.yoi/memory/decisions/Foo.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!( assert!(matches!(
err, err,
@ -355,7 +355,7 @@ mod tests {
#[test] #[test]
fn nested_under_record_dir_rejected() { fn nested_under_record_dir_rejected() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/decisions/sub/foo.md")) .classify(&PathBuf::from("/ws/.yoi/memory/decisions/sub/foo.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, LintError::InvalidPath(_))); assert!(matches!(err, LintError::InvalidPath(_)));
} }
@ -363,7 +363,7 @@ mod tests {
#[test] #[test]
fn unknown_top_level_dir_rejected() { fn unknown_top_level_dir_rejected() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/.insomnia/memory/something/foo.md")) .classify(&PathBuf::from("/ws/.yoi/memory/something/foo.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, LintError::InvalidPath(_))); assert!(matches!(err, LintError::InvalidPath(_)));
} }

View File

@ -71,14 +71,14 @@ impl LockFile {
/// Default on-disk path: `<runtime_dir>/pods.json` resolved via /// Default on-disk path: `<runtime_dir>/pods.json` resolved via
/// [`manifest::paths::pod_registry_path`]. Tests should point this /// [`manifest::paths::pod_registry_path`]. Tests should point this
/// elsewhere by setting `INSOMNIA_HOME` or `INSOMNIA_RUNTIME_DIR` to a /// elsewhere by setting `YOI_HOME` or `YOI_RUNTIME_DIR` to a
/// tempdir. /// tempdir.
pub fn default_registry_path() -> io::Result<PathBuf> { pub fn default_registry_path() -> io::Result<PathBuf> {
paths::pod_registry_path().ok_or_else(|| { paths::pod_registry_path().ok_or_else(|| {
io::Error::new( io::Error::new(
io::ErrorKind::NotFound, io::ErrorKind::NotFound,
"could not resolve pods.json path (no INSOMNIA_HOME / \ "could not resolve pods.json path (no YOI_HOME / \
INSOMNIA_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)", YOI_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)",
) )
}) })
} }
@ -190,7 +190,7 @@ mod tests {
fn open_creates_file_with_owner_only_permissions() { fn open_creates_file_with_owner_only_permissions() {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let parent = dir.path().join("insomnia"); let parent = dir.path().join("yoi");
let path = parent.join("pods.json"); let path = parent.join("pods.json");
let _guard = LockFileGuard::open(&path).unwrap(); let _guard = LockFileGuard::open(&path).unwrap();
let file_mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777; let file_mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;

View File

@ -20,8 +20,8 @@ pub(crate) fn sid() -> SegmentId {
/// parallel test's `default_registry_path()` lookup. /// parallel test's `default_registry_path()` lookup.
pub(crate) static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(())); pub(crate) static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
/// Sandbox `INSOMNIA_RUNTIME_DIR` to a tempdir for the duration of /// Sandbox `YOI_RUNTIME_DIR` to a tempdir for the duration of
/// a test; restore the previous value (and any `INSOMNIA_HOME` / /// a test; restore the previous value (and any `YOI_HOME` /
/// `XDG_RUNTIME_DIR` that would otherwise outrank it) on drop. /// `XDG_RUNTIME_DIR` that would otherwise outrank it) on drop.
pub(crate) struct RuntimeDirSandbox { pub(crate) struct RuntimeDirSandbox {
prev_runtime: Option<String>, prev_runtime: Option<String>,
@ -33,16 +33,16 @@ pub(crate) struct RuntimeDirSandbox {
impl RuntimeDirSandbox { impl RuntimeDirSandbox {
pub(crate) fn new(dir: &Path) -> Self { pub(crate) fn new(dir: &Path) -> Self {
let guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev_runtime = std::env::var("INSOMNIA_RUNTIME_DIR").ok(); let prev_runtime = std::env::var("YOI_RUNTIME_DIR").ok();
let prev_home = std::env::var("INSOMNIA_HOME").ok(); let prev_home = std::env::var("YOI_HOME").ok();
let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok(); let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok();
// SAFETY: ENV_LOCK serialises env writes across this test // SAFETY: ENV_LOCK serialises env writes across this test
// module; other modules that touch env vars rely on their // module; other modules that touch env vars rely on their
// own lock or `serial_test`. // own lock or `serial_test`.
unsafe { unsafe {
std::env::remove_var("INSOMNIA_HOME"); std::env::remove_var("YOI_HOME");
std::env::remove_var("XDG_RUNTIME_DIR"); std::env::remove_var("XDG_RUNTIME_DIR");
std::env::set_var("INSOMNIA_RUNTIME_DIR", dir); std::env::set_var("YOI_RUNTIME_DIR", dir);
} }
Self { Self {
prev_runtime, prev_runtime,
@ -57,12 +57,12 @@ impl Drop for RuntimeDirSandbox {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
match &self.prev_runtime { match &self.prev_runtime {
Some(v) => std::env::set_var("INSOMNIA_RUNTIME_DIR", v), Some(v) => std::env::set_var("YOI_RUNTIME_DIR", v),
None => std::env::remove_var("INSOMNIA_RUNTIME_DIR"), None => std::env::remove_var("YOI_RUNTIME_DIR"),
} }
match &self.prev_home { match &self.prev_home {
Some(v) => std::env::set_var("INSOMNIA_HOME", v), Some(v) => std::env::set_var("YOI_HOME", v),
None => std::env::remove_var("INSOMNIA_HOME"), None => std::env::remove_var("YOI_HOME"),
} }
match &self.prev_xdg { match &self.prev_xdg {
Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v), Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v),

View File

@ -18,5 +18,5 @@
### ランタイム ### ランタイム
- `RuntimeDir``$XDG_RUNTIME_DIR/insomnia/{pod_name}/` 配下のランタイムディレクトリ管理(ステータス・履歴のアトミック書き込み) - `RuntimeDir``$XDG_RUNTIME_DIR/yoi/{pod_name}/` 配下のランタイムディレクトリ管理(ステータス・履歴のアトミック書き込み)
- `SocketServer` — Pod Protocol 用 Unix ソケットサーバー - `SocketServer` — Pod Protocol 用 Unix ソケットサーバー

View File

@ -1,6 +1,6 @@
//! Minimal example: Pod running a single prompt with persistence. //! Minimal example: Pod running a single prompt with persistence.
//! //!
//! Demonstrates the core insomnia abstraction — a TOML manifest drives //! Demonstrates the core yoi abstraction — a TOML manifest drives
//! provider selection, model config, and system prompt, while FsStore //! provider selection, model config, and system prompt, while FsStore
//! persists the session to disk automatically. //! persists the session to disk automatically.
//! //!

View File

@ -822,7 +822,7 @@ mod tests {
let runtime_base = root.path().join("runtime"); let runtime_base = root.path().join("runtime");
std::fs::create_dir_all(&runtime_base).unwrap(); std::fs::create_dir_all(&runtime_base).unwrap();
unsafe { unsafe {
std::env::set_var("INSOMNIA_RUNTIME_DIR", &runtime_base); std::env::set_var("YOI_RUNTIME_DIR", &runtime_base);
} }
let store = FsPodStore::new(&store_dir).unwrap(); let store = FsPodStore::new(&store_dir).unwrap();

View File

@ -191,7 +191,7 @@ fn load_single_manifest(
} }
pub async fn run_cli() -> ExitCode { pub async fn run_cli() -> ExitCode {
run_cli_from("insomnia pod", std::env::args_os().skip(1)).await run_cli_from("yoi pod", std::env::args_os().skip(1)).await
} }
pub async fn run_cli_from<I, T>(bin_name: &'static str, args: I) -> ExitCode pub async fn run_cli_from<I, T>(bin_name: &'static str, args: I) -> ExitCode
@ -246,7 +246,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
}; };
// Initialize persistent store. `paths::sessions_dir()` only // Initialize persistent store. `paths::sessions_dir()` only
// returns None when none of INSOMNIA_HOME / INSOMNIA_DATA_DIR / // returns None when none of YOI_HOME / YOI_DATA_DIR /
// HOME is set — surface that as a hard error to match the // HOME is set — surface that as a hard error to match the
// runtime-dir resolution below, rather than silently writing to a // runtime-dir resolution below, rather than silently writing to a
// relative path under cwd. // relative path under cwd.
@ -257,7 +257,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
None => { None => {
eprintln!( eprintln!(
"error: could not resolve sessions directory \ "error: could not resolve sessions directory \
(set --store, INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)" (set --store, YOI_HOME, YOI_DATA_DIR, or HOME)"
); );
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
@ -375,7 +375,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
None => { None => {
eprintln!( eprintln!(
"error: could not resolve runtime directory \ "error: could not resolve runtime directory \
(set INSOMNIA_HOME, INSOMNIA_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)" (set YOI_HOME, YOI_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
); );
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
@ -393,7 +393,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
// (e.g. the TUI's interactive `spawn` flow). Tab-separated so a // (e.g. the TUI's interactive `spawn` flow). Tab-separated so a
// pod name with spaces still parses cleanly. Emit before the // pod name with spaces still parses cleanly. Emit before the
// human line so a stderr-watching parent sees it first. // human line so a stderr-watching parent sees it first.
eprintln!("INSOMNIA-READY\t{pod_name}\t{}", socket_path.display()); eprintln!("YOI-READY\t{pod_name}\t{}", socket_path.display());
eprintln!("pod: {pod_name} listening on {:?}", socket_path); eprintln!("pod: {pod_name} listening on {:?}", socket_path);
tokio::select! { tokio::select! {
@ -443,36 +443,30 @@ permission = "write"
#[test] #[test]
fn user_manifest_flag_is_not_accepted() { fn user_manifest_flag_is_not_accepted() {
let err = let err = Cli::try_parse_from(["yoi pod", "--user-manifest", "manifest.toml"]).unwrap_err();
Cli::try_parse_from(["insomnia pod", "--user-manifest", "manifest.toml"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument); assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
} }
#[test] #[test]
fn subcommand_help_uses_insomnia_pod_invocation() { fn subcommand_help_uses_yoi_pod_invocation() {
let err = parse_cli_from("insomnia pod", ["--help"]).unwrap_err(); let err = parse_cli_from("yoi pod", ["--help"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
let help = err.to_string(); let help = err.to_string();
assert!(help.contains("Usage: insomnia pod"), "{help}"); assert!(help.contains("Usage: yoi pod"), "{help}");
assert!(help.contains("--pod <NAME>"), "{help}"); assert!(help.contains("--pod <NAME>"), "{help}");
} }
#[test] #[test]
fn manifest_conflicts_with_project() { fn manifest_conflicts_with_project() {
let project_err = Cli::try_parse_from([ let project_err =
"insomnia pod", Cli::try_parse_from(["yoi pod", "--manifest", "manifest.toml", "--project", "."])
"--manifest", .unwrap_err();
"manifest.toml",
"--project",
".",
])
.unwrap_err();
assert_eq!(project_err.kind(), clap::error::ErrorKind::ArgumentConflict); assert_eq!(project_err.kind(), clap::error::ErrorKind::ArgumentConflict);
} }
#[test] #[test]
fn overlay_flag_is_not_accepted() { fn overlay_flag_is_not_accepted() {
let err = Cli::try_parse_from(["insomnia pod", "--overlay", "pod.name = 'x'"]).unwrap_err(); let err = Cli::try_parse_from(["yoi pod", "--overlay", "pod.name = 'x'"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument); assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
} }
@ -481,8 +475,8 @@ permission = "write"
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let manifest = tmp.path().join("manifest.toml"); let manifest = tmp.path().join("manifest.toml");
write(&manifest, &manifest_toml("single", tmp.path())); write(&manifest, &manifest_toml("single", tmp.path()));
let cli = Cli::try_parse_from(["insomnia pod", "--manifest", manifest.to_str().unwrap()]) let cli =
.unwrap(); Cli::try_parse_from(["yoi pod", "--manifest", manifest.to_str().unwrap()]).unwrap();
let (manifest, loader) = resolve_manifest(&cli).unwrap(); let (manifest, loader) = resolve_manifest(&cli).unwrap();
@ -496,7 +490,7 @@ permission = "write"
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let profile = tmp.path().join("profile.lua"); let profile = tmp.path().join("profile.lua");
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from([
"insomnia pod", "yoi pod",
"--profile", "--profile",
profile.to_str().unwrap(), profile.to_str().unwrap(),
"--profile-pod-name", "--profile-pod-name",
@ -529,7 +523,7 @@ permission = "write"
fn profile_accepts_source_qualified_discovered_name() { fn profile_accepts_source_qualified_discovered_name() {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from([
"insomnia pod", "yoi pod",
"--profile", "--profile",
"project:coder", "project:coder",
"--profile-pod-name", "--profile-pod-name",
@ -564,7 +558,7 @@ permission = "write"
#[test] #[test]
fn normal_startup_uses_default_profile() { fn normal_startup_uses_default_profile() {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let cli = Cli::try_parse_from(["insomnia pod"]).unwrap(); let cli = Cli::try_parse_from(["yoi pod"]).unwrap();
let mut called = false; let mut called = false;
let (manifest, _loader) = let (manifest, _loader) =
@ -585,7 +579,7 @@ permission = "write"
#[test] #[test]
fn project_flag_no_longer_enables_ambient_manifest_cascade() { fn project_flag_no_longer_enables_ambient_manifest_cascade() {
let cli = Cli::try_parse_from(["insomnia pod", "--project", "."]).unwrap(); let cli = Cli::try_parse_from(["yoi pod", "--project", "."]).unwrap();
let err = resolve_manifest_with_profile_loader(&cli, |_, _| { let err = resolve_manifest_with_profile_loader(&cli, |_, _| {
panic!("default profile loader must not run when deprecated --project is present") panic!("default profile loader must not run when deprecated --project is present")
}) })
@ -597,7 +591,7 @@ permission = "write"
fn pod_flag_conflicts_with_session() { fn pod_flag_conflicts_with_session() {
let segment_id = session_store::new_segment_id(); let segment_id = session_store::new_segment_id();
let segment_id = segment_id.to_string(); let segment_id = segment_id.to_string();
let err = Cli::try_parse_from(["insomnia pod", "--pod", "agent", "--session", &segment_id]) let err = Cli::try_parse_from(["yoi pod", "--pod", "agent", "--session", &segment_id])
.unwrap_err(); .unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict); assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
} }
@ -608,7 +602,7 @@ permission = "write"
let manifest = tmp.path().join("manifest.toml"); let manifest = tmp.path().join("manifest.toml");
write(&manifest, &manifest_toml("from-file", tmp.path())); write(&manifest, &manifest_toml("from-file", tmp.path()));
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from([
"insomnia pod", "yoi pod",
"--manifest", "--manifest",
manifest.to_str().unwrap(), manifest.to_str().unwrap(),
"--pod", "--pod",
@ -640,7 +634,7 @@ permission = "write"
"#, "#,
); );
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from([
"insomnia pod", "yoi pod",
"--manifest", "--manifest",
manifest.to_str().unwrap(), manifest.to_str().unwrap(),
"--pod", "--pod",
@ -657,7 +651,7 @@ permission = "write"
#[test] #[test]
fn pod_flag_with_no_manifest_creates_from_default_profile_with_typed_name() { fn pod_flag_with_no_manifest_creates_from_default_profile_with_typed_name() {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let cli = Cli::try_parse_from(["insomnia pod", "--pod", "agent"]).unwrap(); let cli = Cli::try_parse_from(["yoi pod", "--pod", "agent"]).unwrap();
let mut called = false; let mut called = false;
let (manifest, _loader) = let (manifest, _loader) =
@ -683,15 +677,9 @@ permission = "write"
fn profile_conflicts_with_manifest_and_restore_modes() { fn profile_conflicts_with_manifest_and_restore_modes() {
let segment_id = session_store::new_segment_id().to_string(); let segment_id = session_store::new_segment_id().to_string();
for args in [ for args in [
vec!["insomnia pod", "--profile", "p.lua", "--manifest", "m.toml"], vec!["yoi pod", "--profile", "p.lua", "--manifest", "m.toml"],
vec!["insomnia pod", "--profile", "p.lua", "--pod", "agent"], vec!["yoi pod", "--profile", "p.lua", "--pod", "agent"],
vec![ vec!["yoi pod", "--profile", "p.lua", "--session", &segment_id],
"insomnia pod",
"--profile",
"p.lua",
"--session",
&segment_id,
],
] { ] {
let err = Cli::try_parse_from(args).unwrap_err(); let err = Cli::try_parse_from(args).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict); assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
@ -700,14 +688,14 @@ permission = "write"
#[test] #[test]
fn profile_pod_name_requires_profile() { fn profile_pod_name_requires_profile() {
let err = Cli::try_parse_from(["insomnia pod", "--profile-pod-name", "agent"]).unwrap_err(); let err = Cli::try_parse_from(["yoi pod", "--profile-pod-name", "agent"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument); assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
} }
#[test] #[test]
fn profile_pod_name_is_not_restore_pod_flag() { fn profile_pod_name_is_not_restore_pod_flag() {
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from([
"insomnia pod", "yoi pod",
"--profile", "--profile",
"p.lua", "p.lua",
"--profile-pod-name", "--profile-pod-name",
@ -724,13 +712,9 @@ permission = "write"
let single_manifest = tmp.path().join("single.toml"); let single_manifest = tmp.path().join("single.toml");
write(&single_manifest, &manifest_toml("single-file", tmp.path())); write(&single_manifest, &manifest_toml("single-file", tmp.path()));
std::fs::create_dir_all(tmp.path().join("prompts")).unwrap(); std::fs::create_dir_all(tmp.path().join("prompts")).unwrap();
std::fs::create_dir_all(tmp.path().join(".insomnia").join("prompts")).unwrap(); std::fs::create_dir_all(tmp.path().join(".yoi").join("prompts")).unwrap();
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from(["yoi pod", "--manifest", single_manifest.to_str().unwrap()])
"insomnia pod", .unwrap();
"--manifest",
single_manifest.to_str().unwrap(),
])
.unwrap();
let (manifest, loader) = resolve_manifest(&cli).unwrap(); let (manifest, loader) = resolve_manifest(&cli).unwrap();

View File

@ -333,7 +333,7 @@ pub struct Pod<C: LlmClient, St: Store> {
/// [`Self::from_manifest`], or defaults to the builtin pack when a /// [`Self::from_manifest`], or defaults to the builtin pack when a
/// Pod is constructed through lower-level paths that have no loader. /// Pod is constructed through lower-level paths that have no loader.
prompts: Arc<PromptCatalog>, prompts: Arc<PromptCatalog>,
/// Registry loaded from `<workspace>/.insomnia/workflow/*.md` when /// Registry loaded from `<workspace>/.yoi/workflow/*.md` when
/// memory is enabled. Missing memory config keeps this empty. /// memory is enabled. Missing memory config keeps this empty.
workflow_registry: workflow_crate::WorkflowRegistry, workflow_registry: workflow_crate::WorkflowRegistry,
/// Memory workspace layout used by the workflow resolver to load required /// Memory workspace layout used by the workflow resolver to load required
@ -5337,21 +5337,21 @@ mod build_summary_prompt_tests {
let pwd = dir.path().join("workspace"); let pwd = dir.path().join("workspace");
std::fs::create_dir_all(&pwd).unwrap(); std::fs::create_dir_all(&pwd).unwrap();
if let Some(doc) = summary_doc { if let Some(doc) = summary_doc {
std::fs::create_dir_all(pwd.join(".insomnia/memory")).unwrap(); std::fs::create_dir_all(pwd.join(".yoi/memory")).unwrap();
std::fs::write(pwd.join(".insomnia/memory/summary.md"), doc).unwrap(); std::fs::write(pwd.join(".yoi/memory/summary.md"), doc).unwrap();
} }
if include_knowledge { if include_knowledge {
std::fs::create_dir_all(pwd.join(".insomnia/knowledge")).unwrap(); std::fs::create_dir_all(pwd.join(".yoi/knowledge")).unwrap();
std::fs::write( std::fs::write(
pwd.join(".insomnia/knowledge/resident-policy.md"), pwd.join(".yoi/knowledge/resident-policy.md"),
knowledge_doc("knowledge resident desc"), knowledge_doc("knowledge resident desc"),
) )
.unwrap(); .unwrap();
} }
if include_workflow { if include_workflow {
std::fs::create_dir_all(pwd.join(".insomnia/workflow")).unwrap(); std::fs::create_dir_all(pwd.join(".yoi/workflow")).unwrap();
std::fs::write( std::fs::write(
pwd.join(".insomnia/workflow/resident-flow.md"), pwd.join(".yoi/workflow/resident-flow.md"),
workflow_doc("workflow resident desc"), workflow_doc("workflow resident desc"),
) )
.unwrap(); .unwrap();
@ -5379,7 +5379,7 @@ mod build_summary_prompt_tests {
pod.set_resident_workflow_injection(gates.workflows); pod.set_resident_workflow_injection(gates.workflows);
} }
let template = SystemPromptTemplate::parse( let template = SystemPromptTemplate::parse(
"$insomnia/default", "$yoi/default",
crate::prompt::loader::PromptLoader::builtins_only(), crate::prompt::loader::PromptLoader::builtins_only(),
) )
.unwrap(); .unwrap();

View File

@ -18,7 +18,7 @@
//! binary. Must cover every [`PodPrompt`] variant (build-time check). //! binary. Must cover every [`PodPrompt`] variant (build-time check).
//! 2. **user** — `<config_dir>/prompts.toml`, when a caller supplies it. //! 2. **user** — `<config_dir>/prompts.toml`, when a caller supplies it.
//! Optional. //! Optional.
//! 3. **workspace** — `<project>/.insomnia/prompts.toml`, when a caller //! 3. **workspace** — `<project>/.yoi/prompts.toml`, when a caller
//! supplies it. Optional. //! supplies it. Optional.
//! 4. **manifest pack** — `manifest.pod.prompt_pack`, an explicit path //! 4. **manifest pack** — `manifest.pod.prompt_pack`, an explicit path
//! per-Pod. Optional. //! per-Pod. Optional.
@ -258,7 +258,7 @@ struct PackFile {
/// Owns a `minijinja::Environment` with one template registered per /// Owns a `minijinja::Environment` with one template registered per
/// [`PodPrompt`] key (after the 4-layer merge). Includes inside templates /// [`PodPrompt`] key (after the 4-layer merge). Includes inside templates
/// are resolved via a provided [`PromptLoader`], so values can pull from /// are resolved via a provided [`PromptLoader`], so values can pull from
/// `$insomnia` / `$user` / `$workspace`. /// `$yoi` / `$user` / `$workspace`.
pub struct PromptCatalog { pub struct PromptCatalog {
env: Environment<'static>, env: Environment<'static>,
} }
@ -271,7 +271,7 @@ impl std::fmt::Debug for PromptCatalog {
impl PromptCatalog { impl PromptCatalog {
/// Builtin-only catalog. All `{% include %}` references must resolve /// Builtin-only catalog. All `{% include %}` references must resolve
/// through `$insomnia` (user/workspace prefixes are unavailable). /// through `$yoi` (user/workspace prefixes are unavailable).
pub fn builtins_only() -> Result<Arc<Self>, CatalogError> { pub fn builtins_only() -> Result<Arc<Self>, CatalogError> {
Self::load(&PromptLoader::builtins_only(), None) Self::load(&PromptLoader::builtins_only(), None)
} }
@ -707,7 +707,7 @@ interrupt_system_note = "[FROM-MANIFEST-PACK]"
#[test] #[test]
fn value_can_pull_long_text_via_include() { fn value_can_pull_long_text_via_include() {
// A runtime pack that overrides `compact_system` with an // A runtime pack that overrides `compact_system` with an
// `{% include %}` into the same `$insomnia` namespace — exercises // `{% include %}` into the same `$yoi` namespace — exercises
// the template resolver path through all four layers. // the template resolver path through all four layers.
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let pack = tmp.path().join("p.toml"); let pack = tmp.path().join("p.toml");
@ -715,7 +715,7 @@ interrupt_system_note = "[FROM-MANIFEST-PACK]"
&pack, &pack,
r#" r#"
[prompt] [prompt]
compact_system = "PREFIX\n{% include \"$insomnia/internal/compact_system\" %}" compact_system = "PREFIX\n{% include \"$yoi/internal/compact_system\" %}"
"#, "#,
) )
.unwrap(); .unwrap();

View File

@ -4,12 +4,12 @@
//! //!
//! | prefix | location | //! | prefix | location |
//! |--------------|---------------------------------------------------------| //! |--------------|---------------------------------------------------------|
//! | `$insomnia` | builtin, baked into the binary via `include_dir!` | //! | `$yoi` | builtin, baked into the binary via `include_dir!` |
//! | `$user` | `<config_dir>/prompts/` (resolved by `manifest::paths`) | //! | `$user` | `<config_dir>/prompts/` (resolved by `manifest::paths`) |
//! | `$workspace` | `<project>/.insomnia/prompts/` | //! | `$workspace` | `<project>/.yoi/prompts/` |
//! //!
//! A reference is `$<prefix>/<path>` where `<path>` is a `/`-separated //! A reference is `$<prefix>/<path>` where `<path>` is a `/`-separated
//! name without the `.md` extension (e.g. `$insomnia/common/header`). //! name without the `.md` extension (e.g. `$yoi/common/header`).
//! Unqualified names (no `$prefix/` at the front) are resolved relative //! Unqualified names (no `$prefix/` at the front) are resolved relative
//! to an optional current reference — typically the file that issued //! to an optional current reference — typically the file that issued
//! the `{% include %}` — so a prompt library can be authored as a //! the `{% include %}` — so a prompt library can be authored as a
@ -25,13 +25,13 @@ use thiserror::Error;
static BUILTIN_PROMPTS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/../../resources/prompts"); static BUILTIN_PROMPTS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/../../resources/prompts");
const PREFIX_INSOMNIA: &str = "$insomnia"; const PREFIX_YOI: &str = "$yoi";
const PREFIX_USER: &str = "$user"; const PREFIX_USER: &str = "$user";
const PREFIX_WORKSPACE: &str = "$workspace"; const PREFIX_WORKSPACE: &str = "$workspace";
/// Prefix-resolved reference to a prompt asset. Produced by /// Prefix-resolved reference to a prompt asset. Produced by
/// [`PromptLoader::parse_ref`] from a user-supplied string such as /// [`PromptLoader::parse_ref`] from a user-supplied string such as
/// `"$insomnia/default"`. /// `"$yoi/default"`.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct PromptRef { pub struct PromptRef {
prefix: Prefix, prefix: Prefix,
@ -42,7 +42,7 @@ pub struct PromptRef {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Prefix { enum Prefix {
Insomnia, Yoi,
User, User,
Workspace, Workspace,
} }
@ -50,7 +50,7 @@ enum Prefix {
impl Prefix { impl Prefix {
fn as_str(self) -> &'static str { fn as_str(self) -> &'static str {
match self { match self {
Self::Insomnia => PREFIX_INSOMNIA, Self::Yoi => PREFIX_YOI,
Self::User => PREFIX_USER, Self::User => PREFIX_USER,
Self::Workspace => PREFIX_WORKSPACE, Self::Workspace => PREFIX_WORKSPACE,
} }
@ -116,7 +116,7 @@ pub struct PromptLoader {
} }
impl PromptLoader { impl PromptLoader {
/// Loader with only the builtin `$insomnia` library available. /// Loader with only the builtin `$yoi` library available.
/// `$user` / `$workspace` references fail with /// `$user` / `$workspace` references fail with
/// [`LoaderError::PrefixNotConfigured`]. /// [`LoaderError::PrefixNotConfigured`].
pub fn builtins_only() -> Self { pub fn builtins_only() -> Self {
@ -221,7 +221,7 @@ impl PromptLoader {
/// when the prefix is not configured or the file does not exist. /// when the prefix is not configured or the file does not exist.
pub fn load(&self, reference: &PromptRef) -> Result<String, LoaderError> { pub fn load(&self, reference: &PromptRef) -> Result<String, LoaderError> {
match reference.prefix { match reference.prefix {
Prefix::Insomnia => load_from_include_dir(&BUILTIN_PROMPTS, reference), Prefix::Yoi => load_from_include_dir(&BUILTIN_PROMPTS, reference),
Prefix::User => match self.user_dir.as_deref() { Prefix::User => match self.user_dir.as_deref() {
Some(dir) => load_from_dir(dir, reference), Some(dir) => load_from_dir(dir, reference),
None => Err(LoaderError::PrefixNotConfigured { None => Err(LoaderError::PrefixNotConfigured {
@ -252,7 +252,7 @@ impl PromptLoader {
fn parse_prefix(raw: &str, prefix_name: &str) -> Result<Prefix, LoaderError> { fn parse_prefix(raw: &str, prefix_name: &str) -> Result<Prefix, LoaderError> {
match prefix_name { match prefix_name {
"insomnia" => Ok(Prefix::Insomnia), "yoi" => Ok(Prefix::Yoi),
"user" => Ok(Prefix::User), "user" => Ok(Prefix::User),
"workspace" => Ok(Prefix::Workspace), "workspace" => Ok(Prefix::Workspace),
_ => Err(LoaderError::UnknownPrefix { _ => Err(LoaderError::UnknownPrefix {
@ -311,15 +311,15 @@ mod tests {
#[test] #[test]
fn builtin_default_resolves() { fn builtin_default_resolves() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let (r, source) = loader.resolve("$insomnia/default", None).unwrap(); let (r, source) = loader.resolve("$yoi/default", None).unwrap();
assert_eq!(r.to_qualified_string(), "$insomnia/default"); assert_eq!(r.to_qualified_string(), "$yoi/default");
assert!(!source.is_empty()); assert!(!source.is_empty());
} }
#[test] #[test]
fn builtin_subdirectory_lookup() { fn builtin_subdirectory_lookup() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let (_, source) = loader.resolve("$insomnia/common/tool-usage", None).unwrap(); let (_, source) = loader.resolve("$yoi/common/tool-usage", None).unwrap();
assert!(source.contains("tool")); assert!(source.contains("tool"));
} }
@ -346,9 +346,7 @@ mod tests {
#[test] #[test]
fn missing_file_is_hard_error() { fn missing_file_is_hard_error() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let err = loader let err = loader.resolve("$yoi/definitely-missing", None).unwrap_err();
.resolve("$insomnia/definitely-missing", None)
.unwrap_err();
assert!(matches!(err, LoaderError::NotFound { .. })); assert!(matches!(err, LoaderError::NotFound { .. }));
} }
@ -379,20 +377,18 @@ mod tests {
#[test] #[test]
fn unqualified_ref_resolves_relative_to_current() { fn unqualified_ref_resolves_relative_to_current() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let current = loader let current = loader.parse_ref("$yoi/common/tool-usage", None).unwrap();
.parse_ref("$insomnia/common/tool-usage", None)
.unwrap();
// Sibling lookup under the same prefix and directory. // Sibling lookup under the same prefix and directory.
let sibling = loader.parse_ref("workspace", Some(&current)).unwrap(); let sibling = loader.parse_ref("workspace", Some(&current)).unwrap();
assert_eq!(sibling.to_qualified_string(), "$insomnia/common/workspace"); assert_eq!(sibling.to_qualified_string(), "$yoi/common/workspace");
} }
#[test] #[test]
fn unqualified_ref_from_root_file_has_empty_dir() { fn unqualified_ref_from_root_file_has_empty_dir() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let current = loader.parse_ref("$insomnia/default", None).unwrap(); let current = loader.parse_ref("$yoi/default", None).unwrap();
let sibling = loader.parse_ref("other", Some(&current)).unwrap(); let sibling = loader.parse_ref("other", Some(&current)).unwrap();
assert_eq!(sibling.to_qualified_string(), "$insomnia/other"); assert_eq!(sibling.to_qualified_string(), "$yoi/other");
} }
#[test] #[test]
@ -402,8 +398,8 @@ mod tests {
std::fs::write(user_dir.join("custom.md"), "user-body").unwrap(); std::fs::write(user_dir.join("custom.md"), "user-body").unwrap();
let loader = PromptLoader::new(Some(user_dir), None); let loader = PromptLoader::new(Some(user_dir), None);
let current = loader.parse_ref("$insomnia/default", None).unwrap(); let current = loader.parse_ref("$yoi/default", None).unwrap();
// Even with an $insomnia-rooted current, an explicit $user // Even with an $yoi-rooted current, an explicit $user
// prefix must win. // prefix must win.
let (reference, source) = loader.resolve("$user/custom", Some(&current)).unwrap(); let (reference, source) = loader.resolve("$user/custom", Some(&current)).unwrap();
assert_eq!(reference.to_qualified_string(), "$user/custom"); assert_eq!(reference.to_qualified_string(), "$user/custom");
@ -413,7 +409,7 @@ mod tests {
#[test] #[test]
fn traversal_segments_rejected() { fn traversal_segments_rejected() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let err = loader.resolve("$insomnia/../etc/passwd", None).unwrap_err(); let err = loader.resolve("$yoi/../etc/passwd", None).unwrap_err();
assert!(matches!(err, LoaderError::InvalidRef { .. })); assert!(matches!(err, LoaderError::InvalidRef { .. }));
} }
} }

View File

@ -155,7 +155,7 @@ pub struct SystemPromptContext<'a> {
/// Not visible from the template; consumed by the trailing-section /// Not visible from the template; consumed by the trailing-section
/// formatter in [`SystemPromptTemplate::render`]. /// formatter in [`SystemPromptTemplate::render`].
pub agents_md: Option<String>, pub agents_md: Option<String>,
/// The body of `<workspace>/.insomnia/memory/summary.md`, with /// The body of `<workspace>/.yoi/memory/summary.md`, with
/// frontmatter stripped. `None` disables the resident summary section; /// frontmatter stripped. `None` disables the resident summary section;
/// empty strings are ignored by the trailing-section formatter. /// empty strings are ignored by the trailing-section formatter.
pub resident_summary: Option<&'a str>, pub resident_summary: Option<&'a str>,
@ -164,7 +164,7 @@ pub struct SystemPromptContext<'a> {
/// section entirely (memory disabled, or a consolidation worker that opts /// section entirely (memory disabled, or a consolidation worker that opts
/// out); `Some(&[])` also yields no section. /// out); `Some(&[])` also yields no section.
pub resident_knowledge: Option<&'a [ResidentKnowledgeEntry]>, pub resident_knowledge: Option<&'a [ResidentKnowledgeEntry]>,
/// Resident workflow descriptions from `<workspace>/.insomnia/workflow/*` /// Resident workflow descriptions from `<workspace>/.yoi/workflow/*`
/// whose frontmatter has `model_invokation: true`. `None` disables the /// whose frontmatter has `model_invokation: true`. `None` disables the
/// section; consolidation workers opt out together with resident Knowledge. /// section; consolidation workers opt out together with resident Knowledge.
pub resident_workflows: Option<&'a [ResidentWorkflowEntry]>, pub resident_workflows: Option<&'a [ResidentWorkflowEntry]>,
@ -557,9 +557,9 @@ mod tests {
} }
#[test] #[test]
fn instruction_default_resolves_to_insomnia_default() { fn instruction_default_resolves_to_yoi_default() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap(); let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path()); let scope = build_scope(dir.path());
let rendered = tmpl let rendered = tmpl
@ -582,7 +582,7 @@ mod tests {
#[test] #[test]
fn instruction_default_omits_memory_guidance_without_memory_tools() { fn instruction_default_omits_memory_guidance_without_memory_tools() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap(); let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path()); let scope = build_scope(dir.path());
let rendered = tmpl let rendered = tmpl
@ -608,7 +608,7 @@ mod tests {
#[test] #[test]
fn memory_guidance_names_only_available_memory_tools() { fn memory_guidance_names_only_available_memory_tools() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap(); let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path()); let scope = build_scope(dir.path());
let rendered = tmpl let rendered = tmpl
@ -632,7 +632,7 @@ mod tests {
#[test] #[test]
fn pod_orchestration_guidance_is_included_for_pod_management_tools() { fn pod_orchestration_guidance_is_included_for_pod_management_tools() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap(); let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path()); let scope = build_scope(dir.path());
let rendered = tmpl let rendered = tmpl
@ -651,7 +651,7 @@ mod tests {
#[test] #[test]
fn pod_orchestration_guidance_is_omitted_without_pod_management_tools() { fn pod_orchestration_guidance_is_omitted_without_pod_management_tools() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap(); let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path()); let scope = build_scope(dir.path());
let rendered = tmpl let rendered = tmpl
@ -735,7 +735,7 @@ mod tests {
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
std::fs::write( std::fs::write(
tmp.path().join("root.md"), tmp.path().join("root.md"),
"U-ROOT\n{% include \"$insomnia/common/tool-usage\" %}", "U-ROOT\n{% include \"$yoi/common/tool-usage\" %}",
) )
.unwrap(); .unwrap();
let loader = PromptLoader::new(Some(tmp.path().to_path_buf()), None); let loader = PromptLoader::new(Some(tmp.path().to_path_buf()), None);
@ -758,7 +758,7 @@ mod tests {
#[test] #[test]
fn prefix_with_missing_file_is_hard_error() { fn prefix_with_missing_file_is_hard_error() {
let loader = PromptLoader::builtins_only(); let loader = PromptLoader::builtins_only();
let err = SystemPromptTemplate::parse("$insomnia/definitely-missing", loader).unwrap_err(); let err = SystemPromptTemplate::parse("$yoi/definitely-missing", loader).unwrap_err();
assert!(matches!(err, SystemPromptError::LoaderResolve(_))); assert!(matches!(err, SystemPromptError::LoaderResolve(_)));
} }

View File

@ -116,8 +116,8 @@ pub fn default_base() -> Result<PathBuf, io::Error> {
paths::runtime_dir().ok_or_else(|| { paths::runtime_dir().ok_or_else(|| {
io::Error::new( io::Error::new(
io::ErrorKind::NotFound, io::ErrorKind::NotFound,
"could not resolve runtime directory (no INSOMNIA_HOME / \ "could not resolve runtime directory (no YOI_HOME / \
INSOMNIA_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)", YOI_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)",
) )
}) })
} }
@ -203,13 +203,13 @@ mod tests {
let records = vec![SpawnedPodRecord { let records = vec![SpawnedPodRecord {
pod_name: "child".into(), pod_name: "child".into(),
socket_path: "/run/insomnia/child/sock".into(), socket_path: "/run/yoi/child/sock".into(),
scope_delegated: vec![ScopeRule { scope_delegated: vec![ScopeRule {
target: "/tmp/work".into(), target: "/tmp/work".into(),
permission: Permission::Write, permission: Permission::Write,
recursive: true, recursive: true,
}], }],
callback_address: "/run/insomnia/my-pod/sock".into(), callback_address: "/run/yoi/my-pod/sock".into(),
}]; }];
rt.write_spawned_pods(&records).await.unwrap(); rt.write_spawned_pods(&records).await.unwrap();

View File

@ -48,7 +48,7 @@ struct SpawnPodInput {
/// unambiguous profile slug. Raw/path selectors are rejected. /// unambiguous profile slug. Raw/path selectors are rejected.
#[serde(default)] #[serde(default)]
profile: Option<String>, profile: Option<String>,
/// Instruction-file reference (e.g. `$insomnia/default`, `$user/my-agent`). /// Instruction-file reference (e.g. `$yoi/default`, `$user/my-agent`).
#[serde(default)] #[serde(default)]
instruction: Option<String>, instruction: Option<String>,
/// First message sent to the spawned Pod via `Method::Run`. /// First message sent to the spawned Pod via `Method::Run`.
@ -214,7 +214,7 @@ pub struct SpawnPodTool {
/// Path to the spawner's Unix socket. Handed to the child via /// Path to the spawner's Unix socket. Handed to the child via
/// `--callback` so its `PodEvent` callbacks have somewhere to land. /// `--callback` so its `PodEvent` callbacks have somewhere to land.
callback_socket: PathBuf, callback_socket: PathBuf,
/// Root of the `$XDG_RUNTIME_DIR/insomnia/` tree, used to predict /// Root of the `$XDG_RUNTIME_DIR/yoi/` tree, used to predict
/// the spawned Pod's socket path before the child has bound it. /// the spawned Pod's socket path before the child has bound it.
runtime_base: PathBuf, runtime_base: PathBuf,
/// Directory the spawned Pod should run in when the LLM did not /// Directory the spawned Pod should run in when the LLM did not
@ -890,7 +890,7 @@ mod tests {
..Default::default() ..Default::default()
}, },
worker: WorkerManifestConfig { worker: WorkerManifestConfig {
instruction: Some("$insomnia/parent".into()), instruction: Some("$yoi/parent".into()),
language: Some("Parentish".into()), language: Some("Parentish".into()),
max_tokens: Some(1234), max_tokens: Some(1234),
stop_sequences: Some(vec!["STOP".into()]), stop_sequences: Some(vec!["STOP".into()]),
@ -916,8 +916,8 @@ mod tests {
default: Option<&str>, default: Option<&str>,
profiles: &[(&str, &str, &str)], profiles: &[(&str, &str, &str)],
) -> AvailableProfiles { ) -> AvailableProfiles {
let insomnia = project.join(".insomnia"); let yoi = project.join(".yoi");
let profile_dir = insomnia.join("profiles"); let profile_dir = yoi.join("profiles");
std::fs::create_dir_all(&profile_dir).unwrap(); std::fs::create_dir_all(&profile_dir).unwrap();
let mut registry_toml = String::new(); let mut registry_toml = String::new();
if let Some(default) = default { if let Some(default) = default {
@ -928,7 +928,7 @@ mod tests {
std::fs::write(profile_dir.join(file), body).unwrap(); std::fs::write(profile_dir.join(file), body).unwrap();
registry_toml.push_str(&format!("{name} = \"profiles/{file}\"\n")); registry_toml.push_str(&format!("{name} = \"profiles/{file}\"\n"));
} }
let registry_path = insomnia.join("profiles.toml"); let registry_path = yoi.join("profiles.toml");
std::fs::write(&registry_path, registry_toml).unwrap(); std::fs::write(&registry_path, registry_toml).unwrap();
AvailableProfiles { AvailableProfiles {
registry: Some( registry: Some(
@ -963,23 +963,23 @@ mod tests {
} }
const CODER_PROFILE: &str = r#" const CODER_PROFILE: &str = r#"
local profile = require("insomnia.profile") local profile = require("yoi.profile")
local scope = require("insomnia.scope") local scope = require("yoi.scope")
return profile { return profile {
slug = "coder", slug = "coder",
model = { scheme = "anthropic", model_id = "coder-model" }, model = { scheme = "anthropic", model_id = "coder-model" },
worker = { instruction = "$insomnia/coder", language = "Coderish", max_tokens = 2222 }, worker = { instruction = "$yoi/coder", language = "Coderish", max_tokens = 2222 },
scope = scope.workspace_write(), scope = scope.workspace_write(),
} }
"#; "#;
const REVIEWER_PROFILE: &str = r#" const REVIEWER_PROFILE: &str = r#"
local profile = require("insomnia.profile") local profile = require("yoi.profile")
local scope = require("insomnia.scope") local scope = require("yoi.scope")
return profile { return profile {
slug = "reviewer", slug = "reviewer",
model = { scheme = "anthropic", model_id = "reviewer-model" }, model = { scheme = "anthropic", model_id = "reviewer-model" },
worker = { instruction = "$insomnia/reviewer", language = "Reviewerish", max_tokens = 3333 }, worker = { instruction = "$yoi/reviewer", language = "Reviewerish", max_tokens = 3333 },
scope = scope.workspace_write(), scope = scope.workspace_write(),
} }
"#; "#;
@ -997,7 +997,7 @@ return profile {
}; };
let config_json = let config_json =
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap(); build_spawn_config_json("child", "$yoi/default", &[], &model, false).unwrap();
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap(); let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic)); assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic));
@ -1020,7 +1020,7 @@ return profile {
..Default::default() ..Default::default()
}; };
let config_json = let config_json =
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap(); build_spawn_config_json("child", "$yoi/default", &[], &model, false).unwrap();
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap(); let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
assert_eq!( assert_eq!(
parsed.model.ref_.as_deref(), parsed.model.ref_.as_deref(),
@ -1041,7 +1041,7 @@ return profile {
}]; }];
let config_json = let config_json =
build_spawn_config_json("child", "$insomnia/default", &scope, &model, true).unwrap(); build_spawn_config_json("child", "$yoi/default", &scope, &model, true).unwrap();
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap(); let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
assert_eq!( assert_eq!(
parsed.session.as_ref().and_then(|s| s.record_event_trace), parsed.session.as_ref().and_then(|s| s.record_event_trace),
@ -1062,7 +1062,7 @@ return profile {
..Default::default() ..Default::default()
}; };
let config_json = let config_json =
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap(); build_spawn_config_json("child", "$yoi/default", &[], &model, false).unwrap();
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap(); let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
assert!(parsed.session.is_none()); assert!(parsed.session.is_none());
@ -1098,10 +1098,7 @@ return profile {
assert_eq!(config.pod.name.as_deref(), Some("child-default")); assert_eq!(config.pod.name.as_deref(), Some("child-default"));
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model")); assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
assert_eq!( assert_eq!(config.worker.instruction.as_deref(), Some("$yoi/reviewer"));
config.worker.instruction.as_deref(),
Some("$insomnia/reviewer")
);
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish")); assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
assert_eq!(config.scope.allow, scope); assert_eq!(config.scope.allow, scope);
assert!(config.scope.deny.is_empty()); assert!(config.scope.deny.is_empty());
@ -1140,10 +1137,7 @@ return profile {
assert_eq!(config.pod.name.as_deref(), Some("review-child")); assert_eq!(config.pod.name.as_deref(), Some("review-child"));
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model")); assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
assert_eq!( assert_eq!(config.worker.instruction.as_deref(), Some("$yoi/reviewer"));
config.worker.instruction.as_deref(),
Some("$insomnia/reviewer")
);
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish")); assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
assert_eq!(config.worker.max_tokens, Some(3333)); assert_eq!(config.worker.max_tokens, Some(3333));
assert_eq!(config.scope.allow, scope); assert_eq!(config.scope.allow, scope);
@ -1177,10 +1171,7 @@ return profile {
assert_eq!(config.pod.name.as_deref(), Some("inherited-child")); assert_eq!(config.pod.name.as_deref(), Some("inherited-child"));
assert_eq!(config.model.model_id.as_deref(), Some("parent-model")); assert_eq!(config.model.model_id.as_deref(), Some("parent-model"));
assert_eq!( assert_eq!(config.worker.instruction.as_deref(), Some("$yoi/parent"));
config.worker.instruction.as_deref(),
Some("$insomnia/parent")
);
assert_eq!(config.worker.language.as_deref(), Some("Parentish")); assert_eq!(config.worker.language.as_deref(), Some("Parentish"));
assert_eq!(config.worker.max_tokens, Some(1234)); assert_eq!(config.worker.max_tokens, Some(1234));
assert_eq!( assert_eq!(
@ -1313,7 +1304,7 @@ return profile {
let user_config = tmp.path().join("user-profiles.toml"); let user_config = tmp.path().join("user-profiles.toml");
std::fs::write(&user_config, "[profile]\ncoder = \"user-coder.lua\"\n").unwrap(); std::fs::write(&user_config, "[profile]\ncoder = \"user-coder.lua\"\n").unwrap();
let project_config = project.join(".insomnia/profiles.toml"); let project_config = project.join(".yoi/profiles.toml");
let ambiguous = AvailableProfiles { let ambiguous = AvailableProfiles {
registry: Some( registry: Some(
ProfileDiscovery::with_sources(Some(user_config), Some(project_config)) ProfileDiscovery::with_sources(Some(user_config), Some(project_config))

View File

@ -145,11 +145,11 @@ mod tests {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
write( write(
&dir.path().join(".insomnia/knowledge/policy.md"), &dir.path().join(".yoi/knowledge/policy.md"),
"---\ncreated_at: 2026-01-01T00:00:00Z\nupdated_at: 2026-01-01T00:00:00Z\nkind: policy\ndescription: p\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\npolicy body\n", "---\ncreated_at: 2026-01-01T00:00:00Z\nupdated_at: 2026-01-01T00:00:00Z\nkind: policy\ndescription: p\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\npolicy body\n",
); );
write( write(
&dir.path().join(".insomnia/workflow/run-it.md"), &dir.path().join(".yoi/workflow/run-it.md"),
"---\ndescription: run\nrequires: [policy]\n---\nworkflow body\n", "---\ndescription: run\nrequires: [policy]\n---\nworkflow body\n",
); );
let registry = workflow_crate::load_workflows(&layout).unwrap(); let registry = workflow_crate::load_workflows(&layout).unwrap();
@ -173,7 +173,7 @@ mod tests {
fn user_invocable_false_errors() { fn user_invocable_false_errors() {
let (dir, layout, _registry) = setup(); let (dir, layout, _registry) = setup();
write( write(
&dir.path().join(".insomnia/workflow/hidden.md"), &dir.path().join(".yoi/workflow/hidden.md"),
"---\ndescription: hidden\nuser_invocable: false\n---\nbody\n", "---\ndescription: hidden\nuser_invocable: false\n---\nbody\n",
); );
let registry = workflow_crate::load_workflows(&layout).unwrap(); let registry = workflow_crate::load_workflows(&layout).unwrap();
@ -186,7 +186,7 @@ mod tests {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
write( write(
&dir.path().join(".insomnia/workflow/bad.md"), &dir.path().join(".yoi/workflow/bad.md"),
"---\ndescription: bad\nrequires: [ghost]\n---\nbody\n", "---\ndescription: bad\nrequires: [ghost]\n---\nbody\n",
); );
let registry = workflow_crate::load_workflows(&layout).unwrap(); let registry = workflow_crate::load_workflows(&layout).unwrap();

View File

@ -29,11 +29,11 @@ use tokio::sync::mpsc;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
/// Serialises env-mutating tests. The test harness runs tasks across /// Serialises env-mutating tests. The test harness runs tasks across
/// threads, and `INSOMNIA_RUNTIME_DIR` is a process-wide resource. /// threads, and `YOI_RUNTIME_DIR` is a process-wide resource.
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(())); static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
/// Take `ENV_LOCK` and clear any env vars that would outrank /// Take `ENV_LOCK` and clear any env vars that would outrank
/// `INSOMNIA_RUNTIME_DIR` in `paths::runtime_dir` resolution; restore /// `YOI_RUNTIME_DIR` in `paths::runtime_dir` resolution; restore
/// previous values on drop. /// previous values on drop.
struct EnvGuard { struct EnvGuard {
prev_home: Option<String>, prev_home: Option<String>,
@ -44,10 +44,10 @@ struct EnvGuard {
impl EnvGuard { impl EnvGuard {
fn acquire() -> Self { fn acquire() -> Self {
let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev_home = std::env::var("INSOMNIA_HOME").ok(); let prev_home = std::env::var("YOI_HOME").ok();
let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok(); let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok();
unsafe { unsafe {
std::env::remove_var("INSOMNIA_HOME"); std::env::remove_var("YOI_HOME");
std::env::remove_var("XDG_RUNTIME_DIR"); std::env::remove_var("XDG_RUNTIME_DIR");
} }
Self { Self {
@ -62,14 +62,14 @@ impl Drop for EnvGuard {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
match &self.prev_home { match &self.prev_home {
Some(v) => std::env::set_var("INSOMNIA_HOME", v), Some(v) => std::env::set_var("YOI_HOME", v),
None => std::env::remove_var("INSOMNIA_HOME"), None => std::env::remove_var("YOI_HOME"),
} }
match &self.prev_xdg { match &self.prev_xdg {
Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v), Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v),
None => std::env::remove_var("XDG_RUNTIME_DIR"), None => std::env::remove_var("XDG_RUNTIME_DIR"),
} }
std::env::remove_var("INSOMNIA_RUNTIME_DIR"); std::env::remove_var("YOI_RUNTIME_DIR");
} }
} }
} }
@ -400,7 +400,7 @@ async fn stop_pod_sends_shutdown_and_releases_scope() {
.unwrap(), .unwrap(),
); );
unsafe { unsafe {
std::env::set_var("INSOMNIA_RUNTIME_DIR", tmp.path()); std::env::set_var("YOI_RUNTIME_DIR", tmp.path());
} }
let lock_path = tmp.path().join("pods.json"); let lock_path = tmp.path().join("pods.json");
@ -486,7 +486,7 @@ async fn stop_pod_succeeds_even_when_child_unreachable() {
let _env = EnvGuard::acquire(); let _env = EnvGuard::acquire();
let (tmp, registry, _rd) = setup_registry().await; let (tmp, registry, _rd) = setup_registry().await;
unsafe { unsafe {
std::env::set_var("INSOMNIA_RUNTIME_DIR", tmp.path()); std::env::set_var("YOI_RUNTIME_DIR", tmp.path());
} }
// No live listener — socket never bound. Registered record points // No live listener — socket never bound. Registered record points
@ -518,7 +518,7 @@ async fn restored_registry_uses_pod_state_without_runtime_file() {
FsPodStore::new(store_tmp.path().join("pods")).unwrap(), FsPodStore::new(store_tmp.path().join("pods")).unwrap(),
); );
unsafe { unsafe {
std::env::set_var("INSOMNIA_RUNTIME_DIR", runtime_tmp.path()); std::env::set_var("YOI_RUNTIME_DIR", runtime_tmp.path());
} }
let rd = Arc::new( let rd = Arc::new(
@ -633,7 +633,7 @@ async fn load_from_pod_state_reclaims_missing_child_scope_and_records_history()
FsPodStore::new(store_tmp.path().join("pods")).unwrap(), FsPodStore::new(store_tmp.path().join("pods")).unwrap(),
); );
unsafe { unsafe {
std::env::set_var("INSOMNIA_RUNTIME_DIR", runtime_tmp.path()); std::env::set_var("YOI_RUNTIME_DIR", runtime_tmp.path());
} }
let rd = Arc::new( let rd = Arc::new(
RuntimeDir::create(runtime_tmp.path(), "spawner") RuntimeDir::create(runtime_tmp.path(), "spawner")

View File

@ -18,11 +18,11 @@ use protocol::{Event, Greeting, Method, Permission, PodEvent, PodStatus, ScopeRu
use tempfile::TempDir; use tempfile::TempDir;
use tokio::net::UnixListener; use tokio::net::UnixListener;
/// Serialises tests that mutate `INSOMNIA_RUNTIME_DIR`. /// Serialises tests that mutate `YOI_RUNTIME_DIR`.
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(())); static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
/// Take `ENV_LOCK` and clear any env vars that would outrank /// Take `ENV_LOCK` and clear any env vars that would outrank
/// `INSOMNIA_RUNTIME_DIR`; restore previous values on drop. /// `YOI_RUNTIME_DIR`; restore previous values on drop.
struct EnvGuard { struct EnvGuard {
prev_home: Option<String>, prev_home: Option<String>,
prev_xdg: Option<String>, prev_xdg: Option<String>,
@ -32,10 +32,10 @@ struct EnvGuard {
impl EnvGuard { impl EnvGuard {
fn acquire() -> Self { fn acquire() -> Self {
let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev_home = std::env::var("INSOMNIA_HOME").ok(); let prev_home = std::env::var("YOI_HOME").ok();
let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok(); let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok();
unsafe { unsafe {
std::env::remove_var("INSOMNIA_HOME"); std::env::remove_var("YOI_HOME");
std::env::remove_var("XDG_RUNTIME_DIR"); std::env::remove_var("XDG_RUNTIME_DIR");
} }
Self { Self {
@ -50,29 +50,29 @@ impl Drop for EnvGuard {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
match &self.prev_home { match &self.prev_home {
Some(v) => std::env::set_var("INSOMNIA_HOME", v), Some(v) => std::env::set_var("YOI_HOME", v),
None => std::env::remove_var("INSOMNIA_HOME"), None => std::env::remove_var("YOI_HOME"),
} }
match &self.prev_xdg { match &self.prev_xdg {
Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v), Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v),
None => std::env::remove_var("XDG_RUNTIME_DIR"), None => std::env::remove_var("XDG_RUNTIME_DIR"),
} }
std::env::remove_var("INSOMNIA_RUNTIME_DIR"); std::env::remove_var("YOI_RUNTIME_DIR");
} }
} }
} }
/// Point `INSOMNIA_RUNTIME_DIR` at `dir`. The pod-registry then lives at /// Point `YOI_RUNTIME_DIR` at `dir`. The pod-registry then lives at
/// `<dir>/pods.json` and Pod runtime sub-dirs at `<dir>/{pod_name}/`. /// `<dir>/pods.json` and Pod runtime sub-dirs at `<dir>/{pod_name}/`.
fn set_runtime_dir(dir: &std::path::Path) { fn set_runtime_dir(dir: &std::path::Path) {
unsafe { unsafe {
std::env::set_var("INSOMNIA_RUNTIME_DIR", dir); std::env::set_var("YOI_RUNTIME_DIR", dir);
} }
} }
fn clear_runtime_dir() { fn clear_runtime_dir() {
unsafe { unsafe {
std::env::remove_var("INSOMNIA_RUNTIME_DIR"); std::env::remove_var("YOI_RUNTIME_DIR");
} }
} }

View File

@ -26,7 +26,7 @@ use std::sync::Arc;
use tempfile::TempDir; use tempfile::TempDir;
use tokio::net::UnixListener; use tokio::net::UnixListener;
/// Serialises tests that mutate `INSOMNIA_RUNTIME_DIR` across the /// Serialises tests that mutate `YOI_RUNTIME_DIR` across the
/// thread-pooled test harness. /// thread-pooled test harness.
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(())); static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
@ -42,7 +42,7 @@ impl EnvGuard {
} }
} }
/// Set up a tempdir, point `INSOMNIA_RUNTIME_DIR` at it (so /// Set up a tempdir, point `YOI_RUNTIME_DIR` at it (so
/// `pods.json` and per-Pod runtime subdirs both land in the /// `pods.json` and per-Pod runtime subdirs both land in the
/// sandbox), and install a live top-level "spawner" allocation so the /// sandbox), and install a live top-level "spawner" allocation so the
/// tool has something to delegate from. Returns the tempdir (keeps it /// tool has something to delegate from. Returns the tempdir (keeps it
@ -57,9 +57,9 @@ async fn setup_spawner(
unsafe { unsafe {
// Outranking env vars must be cleared so `paths::runtime_dir` // Outranking env vars must be cleared so `paths::runtime_dir`
// resolves to our sandbox instead of the developer's real one. // resolves to our sandbox instead of the developer's real one.
std::env::remove_var("INSOMNIA_HOME"); std::env::remove_var("YOI_HOME");
std::env::remove_var("XDG_RUNTIME_DIR"); std::env::remove_var("XDG_RUNTIME_DIR");
std::env::set_var("INSOMNIA_RUNTIME_DIR", &runtime_base); std::env::set_var("YOI_RUNTIME_DIR", &runtime_base);
} }
let spawner_rd = RuntimeDir::create(&runtime_base, spawner_name) let spawner_rd = RuntimeDir::create(&runtime_base, spawner_name)
@ -209,7 +209,7 @@ fn shared_scope_for(allow_root: &Path) -> SharedScope {
fn clear_env() { fn clear_env() {
unsafe { unsafe {
std::env::remove_var("INSOMNIA_RUNTIME_DIR"); std::env::remove_var("YOI_RUNTIME_DIR");
} }
} }

View File

@ -1258,7 +1258,7 @@ mod tests {
let method = Method::PodEvent(PodEvent::ScopeSubDelegated { let method = Method::PodEvent(PodEvent::ScopeSubDelegated {
parent_pod: "child".into(), parent_pod: "child".into(),
sub_pod: "grandchild".into(), sub_pod: "grandchild".into(),
sub_socket: "/run/insomnia/grandchild/sock".into(), sub_socket: "/run/yoi/grandchild/sock".into(),
scope: vec![ScopeRule { scope: vec![ScopeRule {
target: "/tmp/work".into(), target: "/tmp/work".into(),
permission: Permission::Write, permission: Permission::Write,
@ -1276,7 +1276,7 @@ mod tests {
}) => { }) => {
assert_eq!(parent_pod, "child"); assert_eq!(parent_pod, "child");
assert_eq!(sub_pod, "grandchild"); assert_eq!(sub_pod, "grandchild");
assert_eq!(sub_socket, PathBuf::from("/run/insomnia/grandchild/sock")); assert_eq!(sub_socket, PathBuf::from("/run/yoi/grandchild/sock"));
assert_eq!(scope.len(), 1); assert_eq!(scope.len(), 1);
assert_eq!(scope[0].target, PathBuf::from("/tmp/work")); assert_eq!(scope[0].target, PathBuf::from("/tmp/work"));
assert_eq!(scope[0].permission, Permission::Write); assert_eq!(scope[0].permission, Permission::Write);

View File

@ -12,7 +12,7 @@
## 責務 ## 責務
- プロバイダ / モデルカタログの builtin (`resources/{providers,models}/builtin.toml`) と user override (`$XDG_CONFIG_HOME/insomnia/{providers,models}.toml`) の解決 - プロバイダ / モデルカタログの builtin (`resources/{providers,models}/builtin.toml`) と user override (`$XDG_CONFIG_HOME/yoi/{providers,models}.toml`) の解決
- `ModelManifest` の ref 形を `(provider, model_id)` に split し、`ModelConfig` へ展開 - `ModelManifest` の ref 形を `(provider, model_id)` に split し、`ModelConfig` へ展開
- `AuthRef::SecretRef` / `AuthRef::ApiKey``ResolvedAuth::ApiKey` に解決(通常は local secret store、低レベル manifest では明示ファイルも可) - `AuthRef::SecretRef` / `AuthRef::ApiKey``ResolvedAuth::ApiKey` に解決(通常は local secret store、低レベル manifest では明示ファイルも可)
- `AuthRef::None` / `AuthRef::CodexOAuth` の解決 - `AuthRef::None` / `AuthRef::CodexOAuth` の解決

View File

@ -636,9 +636,9 @@ auth_hint = { kind = "none" }
assert!(matches!(err, CatalogError::Parse { .. })); assert!(matches!(err, CatalogError::Parse { .. }));
} }
/// `INSOMNIA_CONFIG_DIR` を tempdir に向けるテストガード。 /// `YOI_CONFIG_DIR` を tempdir に向けるテストガード。
/// `paths::config_dir` は他の env (INSOMNIA_HOME / XDG_CONFIG_HOME) /// `paths::config_dir` は他の env (YOI_HOME / XDG_CONFIG_HOME)
/// より高優先で `INSOMNIA_CONFIG_DIR` を尊重するため、これだけで /// より高優先で `YOI_CONFIG_DIR` を尊重するため、これだけで
/// 開発機の env 設定に左右されないテストになる。 /// 開発機の env 設定に左右されないテストになる。
struct ConfigDirGuard { struct ConfigDirGuard {
prev: Option<String>, prev: Option<String>,
@ -646,10 +646,10 @@ auth_hint = { kind = "none" }
impl ConfigDirGuard { impl ConfigDirGuard {
fn new(path: &Path) -> Self { fn new(path: &Path) -> Self {
let prev = std::env::var("INSOMNIA_CONFIG_DIR").ok(); let prev = std::env::var("YOI_CONFIG_DIR").ok();
// SAFETY: serial_test の `#[serial]` 属性で env を弄るテスト // SAFETY: serial_test の `#[serial]` 属性で env を弄るテスト
// 同士は直列化される。 // 同士は直列化される。
unsafe { std::env::set_var("INSOMNIA_CONFIG_DIR", path) }; unsafe { std::env::set_var("YOI_CONFIG_DIR", path) };
Self { prev } Self { prev }
} }
} }
@ -658,8 +658,8 @@ auth_hint = { kind = "none" }
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
match &self.prev { match &self.prev {
Some(v) => std::env::set_var("INSOMNIA_CONFIG_DIR", v), Some(v) => std::env::set_var("YOI_CONFIG_DIR", v),
None => std::env::remove_var("INSOMNIA_CONFIG_DIR"), None => std::env::remove_var("YOI_CONFIG_DIR"),
} }
} }
} }

View File

@ -1,6 +1,6 @@
//! `~/.codex/auth.json` の読み書き。 //! `~/.codex/auth.json` の読み書き。
//! //!
//! Codex CLI と schema を共有するが、insomnia は知らないフィールドを //! Codex CLI と schema を共有するが、yoi は知らないフィールドを
//! 失わないようファイル全体を `serde_json::Value` で保持し、必要箇所 //! 失わないようファイル全体を `serde_json::Value` で保持し、必要箇所
//! のみアクセスする。書込は `mode 0o600` を再設定Codex CLI 同様)、 //! のみアクセスする。書込は `mode 0o600` を再設定Codex CLI 同様)、
//! ファイルロックは取らないmanager 側で guarded reload //! ファイルロックは取らないmanager 側で guarded reload
@ -106,7 +106,7 @@ pub async fn load(path: &Path) -> Result<AuthSnapshot, CodexAuthError> {
/// 既存ファイルを再読込し、`tokens.{id_token,access_token,refresh_token}` と /// 既存ファイルを再読込し、`tokens.{id_token,access_token,refresh_token}` と
/// `last_refresh` を更新して書き戻す。Codex CLI の `persist_tokens` 相当。 /// `last_refresh` を更新して書き戻す。Codex CLI の `persist_tokens` 相当。
/// ///
/// 並行する Codex CLI / 別 insomnia プロセスが先に refresh していた場合の /// 並行する Codex CLI / 別 yoi プロセスが先に refresh していた場合の
/// fields を保護するため、書込前に再 load して merge する。 /// fields を保護するため、書込前に再 load して merge する。
pub async fn persist_refreshed( pub async fn persist_refreshed(
path: &Path, path: &Path,

View File

@ -8,7 +8,7 @@
//! [`CodexAuthProvider`] はこのクレートに置くfeedback_llm_worker_scope //! [`CodexAuthProvider`] はこのクレートに置くfeedback_llm_worker_scope
//! - access_token JWT の `exp` を読み、`now` 以下で proactive refresh //! - access_token JWT の `exp` を読み、`now` 以下で proactive refresh
//! Codex CLI と同じバッファなし) //! Codex CLI と同じバッファなし)
//! - 並行する Codex CLI / 別 insomnia の refresh と取り違えないよう、 //! - 並行する Codex CLI / 別 yoi の refresh と取り違えないよう、
//! refresh 直前に再 load して account_id 一致を確認guarded reload //! refresh 直前に再 load して account_id 一致を確認guarded reload
//! - ファイルロックは取らず、書込前に再 load + diff merge で吸収 //! - ファイルロックは取らず、書込前に再 load + diff merge で吸収
//! - Codex の Keyring storage は対象外。auth.json 不在ならエラーで案内 //! - Codex の Keyring storage は対象外。auth.json 不在ならエラーで案内

View File

@ -78,7 +78,7 @@ impl SecretResolver for DefaultSecretResolver {
path: std::path::PathBuf::from("<data_dir>"), path: std::path::PathBuf::from("<data_dir>"),
source: std::io::Error::new( source: std::io::Error::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
"could not determine insomnia data directory", "could not determine yoi data directory",
), ),
})?; })?;
SecretStore::new(data_dir).get(id) SecretStore::new(data_dir).get(id)

View File

@ -305,7 +305,7 @@ pub fn validate_id(id: &str) -> Result<()> {
fn derive_key(data_dir: &Path) -> [u8; KEY_LEN] { fn derive_key(data_dir: &Path) -> [u8; KEY_LEN] {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(b"insomnia local secret store obfuscation key v1"); hasher.update(b"yoi local secret store obfuscation key v1");
hasher.update(data_dir.as_os_str().as_encoded_bytes()); hasher.update(data_dir.as_os_str().as_encoded_bytes());
hasher.finalize().into() hasher.finalize().into()
} }
@ -339,7 +339,7 @@ fn make_nonce(id: &str, plaintext: &[u8]) -> Vec<u8> {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
.as_nanos(); .as_nanos();
hasher.update(b"insomnia nonce v1"); hasher.update(b"yoi nonce v1");
hasher.update(now.to_le_bytes()); hasher.update(now.to_le_bytes());
hasher.update(std::process::id().to_le_bytes()); hasher.update(std::process::id().to_le_bytes());
hasher.update(NONCE_COUNTER.fetch_add(1, Ordering::Relaxed).to_le_bytes()); hasher.update(NONCE_COUNTER.fetch_add(1, Ordering::Relaxed).to_le_bytes());
@ -353,7 +353,7 @@ fn xor_stream(key: &[u8; KEY_LEN], nonce: &[u8], input: &[u8]) -> Vec<u8> {
let mut counter = 0u64; let mut counter = 0u64;
for chunk in input.chunks(KEY_LEN) { for chunk in input.chunks(KEY_LEN) {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(b"insomnia secret keystream v1"); hasher.update(b"yoi secret keystream v1");
hasher.update(key); hasher.update(key);
hasher.update(nonce); hasher.update(nonce);
hasher.update(counter.to_le_bytes()); hasher.update(counter.to_le_bytes());
@ -368,7 +368,7 @@ fn xor_stream(key: &[u8; KEY_LEN], nonce: &[u8], input: &[u8]) -> Vec<u8> {
fn tag(key: &[u8; KEY_LEN], id: &str, nonce: &[u8], ciphertext: &[u8]) -> [u8; TAG_LEN] { fn tag(key: &[u8; KEY_LEN], id: &str, nonce: &[u8], ciphertext: &[u8]) -> [u8; TAG_LEN] {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(b"insomnia secret tag v1"); hasher.update(b"yoi secret tag v1");
hasher.update(key); hasher.update(key);
hasher.update(id.as_bytes()); hasher.update(id.as_bytes());
hasher.update(nonce); hasher.update(nonce);

View File

@ -9,7 +9,7 @@
//! //!
//! Migration: this layout is incompatible with the pre-`session-grouping` //! Migration: this layout is incompatible with the pre-`session-grouping`
//! flat `{root}/{segment_id}.jsonl` form. Project policy is no //! flat `{root}/{segment_id}.jsonl` form. Project policy is no
//! backward compatibility — discard `~/.insomnia/sessions/` (or whatever //! backward compatibility — discard `~/.yoi/sessions/` (or whatever
//! `root` resolved to) before running the new code. `list_sessions` //! `root` resolved to) before running the new code. `list_sessions`
//! ignores top-level files outside session directories, so leftover //! ignores top-level files outside session directories, so leftover
//! flat files do not corrupt new sessions, but they are no longer //! flat files do not corrupt new sessions, but they are no longer

View File

@ -141,7 +141,7 @@ impl Tool for BashTool {
// close before bash itself exits. // close before bash itself exits.
// exit $__exit propagate the user's exit // exit $__exit propagate the user's exit
let wrapped = format!( let wrapped = format!(
"exec >{out} 2>&1\n{{ {user_cmd}\n}}\n__insomnia_exit=$?\nwait 2>/dev/null\nexit $__insomnia_exit\n", "exec >{out} 2>&1\n{{ {user_cmd}\n}}\n__yoi_exit=$?\nwait 2>/dev/null\nexit $__yoi_exit\n",
out = shell_single_quote(output_path_str), out = shell_single_quote(output_path_str),
user_cmd = params.command, user_cmd = params.command,
); );

View File

@ -1,4 +1,4 @@
//! Built-in tools for the Insomnia LLM agent. //! Built-in tools for the Yoi LLM agent.
//! //!
//! Implements Read / Write / Edit / Glob / Grep / Bash on top of the //! Implements Read / Write / Edit / Glob / Grep / Bash on top of the
//! `llm-worker` `Tool` infrastructure. Filesystem access is mediated by //! `llm-worker` `Tool` infrastructure. Filesystem access is mediated by

View File

@ -33,7 +33,7 @@
//! let scope = Scope::writable("/workspace").unwrap(); //! let scope = Scope::writable("/workspace").unwrap();
//! let fs = ScopedFs::new(scope, PathBuf::from("/workspace")); // pod lifetime //! let fs = ScopedFs::new(scope, PathBuf::from("/workspace")); // pod lifetime
//! let tracker = Tracker::new(); // session lifetime //! let tracker = Tracker::new(); // session lifetime
//! let bash_outputs = PathBuf::from("/run/insomnia/bash-output"); //! let bash_outputs = PathBuf::from("/run/yoi/bash-output");
//! let task_store = tools::TaskStore::new(); //! let task_store = tools::TaskStore::new();
//! let defs = builtin_tools(fs, tracker, task_store, bash_outputs, None); //! let defs = builtin_tools(fs, tracker, task_store, bash_outputs, None);
//! ``` //! ```

View File

@ -44,7 +44,7 @@ impl WebTools {
pub fn new(config: Option<WebConfig>) -> Self { pub fn new(config: Option<WebConfig>) -> Self {
let client = Client::builder() let client = Client::builder()
.redirect(reqwest::redirect::Policy::none()) .redirect(reqwest::redirect::Policy::none())
.user_agent("insomnia-web-tools/0.1") .user_agent("yoi-web-tools/0.1")
.build() .build()
.expect("static reqwest client configuration is valid"); .expect("static reqwest client configuration is valid");
let secret_store = manifest::paths::data_dir().map(SecretStore::new); let secret_store = manifest::paths::data_dir().map(SecretStore::new);
@ -249,7 +249,7 @@ async fn brave_search(
let api_key_secret = cfg.api_key_secret.as_ref().ok_or_else(|| { let api_key_secret = cfg.api_key_secret.as_ref().ok_or_else(|| {
disabled_error( disabled_error(
"WebSearch", "WebSearch",
"set web.search.api_key_secret to the insomnia keys secret id for the Brave API key", "set web.search.api_key_secret to the yoi keys secret id for the Brave API key",
) )
})?; })?;
let store = secret_store.ok_or_else(|| { let store = secret_store.ok_or_else(|| {
@ -1783,7 +1783,7 @@ mod tests {
); );
let search_err = tools let search_err = tools
.run_search(WebSearchInput { .run_search(WebSearchInput {
query: "insomnia".into(), query: "yoi".into(),
limit: None, limit: None,
offset: None, offset: None,
}) })
@ -2099,7 +2099,7 @@ mod tests {
); );
let result = tools let result = tools
.run_search(WebSearchInput { .run_search(WebSearchInput {
query: "insomnia".into(), query: "yoi".into(),
limit: Some(1), limit: Some(1),
offset: Some(0), offset: Some(0),
}) })
@ -2126,12 +2126,12 @@ mod tests {
fetch: None, fetch: None,
})); }));
let cfg = brave_search_config(format!("http://{addr}/search")); let cfg = brave_search_config(format!("http://{addr}/search"));
let result = brave_search_with_api_key(&tools.client, &cfg, "test-key", "insomnia", 1, 0) let result = brave_search_with_api_key(&tools.client, &cfg, "test-key", "yoi", 1, 0)
.await .await
.unwrap(); .unwrap();
let value: Value = serde_json::from_str(result.content.as_deref().unwrap()).unwrap(); let value: Value = serde_json::from_str(result.content.as_deref().unwrap()).unwrap();
let request = captured.lock().await.clone().unwrap(); let request = captured.lock().await.clone().unwrap();
assert!(request.starts_with("GET /search?q=insomnia&count=1&offset=0 ")); assert!(request.starts_with("GET /search?q=yoi&count=1&offset=0 "));
assert!( assert!(
request request
.to_ascii_lowercase() .to_ascii_lowercase()
@ -2158,7 +2158,7 @@ mod tests {
fetch: None, fetch: None,
})); }));
let cfg = brave_search_config(format!("http://{addr}/search")); let cfg = brave_search_config(format!("http://{addr}/search"));
let err = brave_search_with_api_key(&tools.client, &cfg, "test-key", "insomnia", 1, 0) let err = brave_search_with_api_key(&tools.client, &cfg, "test-key", "yoi", 1, 0)
.await .await
.unwrap_err(); .unwrap_err();
assert!(err.to_string().contains("Content-Length")); assert!(err.to_string().contains("Content-Length"));

View File

@ -221,14 +221,14 @@ pub async fn launch() -> ExitCode {
let data_dir = match manifest::paths::data_dir() { let data_dir = match manifest::paths::data_dir() {
Some(path) => path, Some(path) => path,
None => { None => {
eprintln!("insomnia keys: could not determine insomnia data directory"); eprintln!("yoi keys: could not determine yoi data directory");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };
match run(SecretStore::new(data_dir)) { match run(SecretStore::new(data_dir)) {
Ok(()) => ExitCode::SUCCESS, Ok(()) => ExitCode::SUCCESS,
Err(err) => { Err(err) => {
eprintln!("insomnia keys: {err}"); eprintln!("yoi keys: {err}");
ExitCode::FAILURE ExitCode::FAILURE
} }
} }
@ -391,7 +391,7 @@ fn draw(frame: &mut Frame<'_>, app: &KeysApp) {
fn title_line(app: &KeysApp) -> Line<'_> { fn title_line(app: &KeysApp) -> Line<'_> {
Line::from(vec![ Line::from(vec![
Span::styled( Span::styled(
"insomnia keys local secrets", "yoi keys local secrets",
Style::default().add_modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
), ),
Span::raw(" "), Span::raw(" "),

View File

@ -38,20 +38,20 @@ pub enum LaunchMode {
Spawn { Spawn {
profile: Option<String>, profile: Option<String>,
}, },
/// `insomnia <name>` / `insomnia --pod <name>`: attach to a live Pod by name if /// `yoi <name>` / `yoi --pod <name>`: attach to a live Pod by name if
/// possible; otherwise launch the Pod runtime command with `--pod <name>` so it /// possible; otherwise launch the Pod runtime command with `--pod <name>` so it
/// resumes from name-keyed state or creates a fresh same-name Pod. /// resumes from name-keyed state or creates a fresh same-name Pod.
PodName { PodName {
pod_name: String, pod_name: String,
socket_override: Option<PathBuf>, socket_override: Option<PathBuf>,
}, },
/// `insomnia -r` / `insomnia --resume`: open the Pod picker, then attach to the /// `yoi -r` / `yoi --resume`: open the Pod picker, then attach to the
/// selected live Pod or restore the selected stopped Pod by name. /// selected live Pod or restore the selected stopped Pod by name.
Resume, Resume,
/// `insomnia --session <UUID>`: skip the picker, go straight to the /// `yoi --session <UUID>`: skip the picker, go straight to the
/// resume name dialog with `id` baked in. /// resume name dialog with `id` baked in.
ResumeWithSession(SegmentId), ResumeWithSession(SegmentId),
/// `insomnia --multi`: open the multi-Pod dashboard. This is intentionally /// `yoi --multi`: open the multi-Pod dashboard. This is intentionally
/// separate from `-r`/`--resume`, which keeps its single-Pod picker /// separate from `-r`/`--resume`, which keeps its single-Pod picker
/// meaning. /// meaning.
Multi, Multi,
@ -64,12 +64,12 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
} = options; } = options;
if let Err(e) = enable_raw_mode() { if let Err(e) = enable_raw_mode() {
eprintln!("insomnia: failed to enter raw mode: {e}"); eprintln!("yoi: failed to enter raw mode: {e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
if let Err(e) = execute!(io::stdout(), EnableBracketedPaste) { if let Err(e) = execute!(io::stdout(), EnableBracketedPaste) {
let _ = disable_raw_mode(); let _ = disable_raw_mode();
eprintln!("insomnia: {e}"); eprintln!("yoi: {e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
@ -110,7 +110,7 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
// duplicate. Other errors (pod-name failures, terminal setup // duplicate. Other errors (pod-name failures, terminal setup
// hiccups, etc.) need surfacing here. // hiccups, etc.) need surfacing here.
if e.downcast_ref::<spawn::SpawnError>().is_none() { if e.downcast_ref::<spawn::SpawnError>().is_none() {
eprintln!("insomnia: {e}"); eprintln!("yoi: {e}");
} }
ExitCode::FAILURE ExitCode::FAILURE
} }

View File

@ -43,7 +43,7 @@ impl std::fmt::Display for MultiPodError {
Self::Store(e) => write!(f, "session store error: {e}"), Self::Store(e) => write!(f, "session store error: {e}"),
Self::NoPods => write!( Self::NoPods => write!(
f, f,
"no pods found — start a fresh pod with `insomnia` or restore one with `insomnia -r`" "no pods found — start a fresh pod with `yoi` or restore one with `yoi -r`"
), ),
} }
} }

View File

@ -42,7 +42,7 @@ impl std::fmt::Display for PickerError {
Self::Store(e) => write!(f, "session store error: {e}"), Self::Store(e) => write!(f, "session store error: {e}"),
Self::NoPods => write!( Self::NoPods => write!(
f, f,
"no pods found — start a fresh pod with `insomnia` and try again" "no pods found — start a fresh pod with `yoi` and try again"
), ),
} }
} }
@ -169,7 +169,7 @@ fn default_store_dir() -> Result<PathBuf, PickerError> {
PickerError::Io(io::Error::new( PickerError::Io(io::Error::new(
io::ErrorKind::NotFound, io::ErrorKind::NotFound,
"could not resolve sessions directory \ "could not resolve sessions directory \
(set INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)", (set YOI_HOME, YOI_DATA_DIR, or HOME)",
)) ))
}) })
} }
@ -181,7 +181,7 @@ fn default_pod_store_dir() -> Result<PathBuf, PickerError> {
PickerError::Io(io::Error::new( PickerError::Io(io::Error::new(
io::ErrorKind::NotFound, io::ErrorKind::NotFound,
"could not resolve pod state directory \ "could not resolve pod state directory \
(set INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)", (set YOI_HOME, YOI_DATA_DIR, or HOME)",
)) ))
}) })
} }

View File

@ -35,7 +35,7 @@ fn resolve_socket(pod_name: &str, override_path: Option<PathBuf>) -> PathBuf {
} }
manifest::paths::pod_socket_path(pod_name).unwrap_or_else(|| { manifest::paths::pod_socket_path(pod_name).unwrap_or_else(|| {
PathBuf::from("/tmp") PathBuf::from("/tmp")
.join("insomnia") .join("yoi")
.join(pod_name) .join(pod_name)
.join("sock") .join("sock")
}) })
@ -307,7 +307,7 @@ impl TerminalEventReader {
let stop = Arc::new(AtomicBool::new(false)); let stop = Arc::new(AtomicBool::new(false));
let thread_stop = Arc::clone(&stop); let thread_stop = Arc::clone(&stop);
let thread = thread::Builder::new() let thread = thread::Builder::new()
.name("insomnia-tui-terminal-reader".to_string()) .name("yoi-tui-terminal-reader".to_string())
.spawn(move || read_terminal_events(thread_stop, tx))?; .spawn(move || read_terminal_events(thread_stop, tx))?;
Ok(( Ok((

View File

@ -1,11 +1,11 @@
//! Inline-viewport "spawn Pod and attach" UX. //! Inline-viewport "spawn Pod and attach" UX.
//! //!
//! Rendered at the user's current cursor position when `insomnia` is invoked //! Rendered at the user's current cursor position when `yoi` is invoked
//! with no positional argument. Discovers `.insomnia/profiles.toml` profile //! with no positional argument. Discovers `.yoi/profiles.toml` profile
//! choices plus bundled profiles, defaults to the builtin profile, prompts for //! choices plus bundled profiles, defaults to the builtin profile, prompts for
//! the Pod's name, and on confirmation launches the Pod runtime command as an //! the Pod's name, and on confirmation launches the Pod runtime command as an
//! independent process. Once the process reports its socket via the //! independent process. Once the process reports its socket via the
//! `INSOMNIA-READY` stderr line, the dialog hands control back so main can //! `YOI-READY` stderr line, the dialog hands control back so main can
//! switch the terminal to alternate-screen mode. //! switch the terminal to alternate-screen mode.
//! //!
//! The viewport's last frame stays in the terminal's scrollback so the //! The viewport's last frame stays in the terminal's scrollback so the
@ -654,10 +654,10 @@ mod tests {
fn profile_choices_use_project_registry_default() { fn profile_choices_use_project_registry_default() {
let temp = tempfile::tempdir().unwrap(); let temp = tempfile::tempdir().unwrap();
let project = temp.path().join("project"); let project = temp.path().join("project");
let insomnia = project.join(".insomnia"); let yoi = project.join(".yoi");
std::fs::create_dir_all(&insomnia).unwrap(); std::fs::create_dir_all(&yoi).unwrap();
std::fs::write( std::fs::write(
insomnia.join("profiles.toml"), yoi.join("profiles.toml"),
r#" r#"
default = "coder" default = "coder"
[profile] [profile]
@ -678,10 +678,10 @@ coder = "profiles/coder.lua"
fn profile_choices_include_builtin_and_project_default_marker() { fn profile_choices_include_builtin_and_project_default_marker() {
let temp = tempfile::tempdir().unwrap(); let temp = tempfile::tempdir().unwrap();
let project = temp.path().join("project"); let project = temp.path().join("project");
let insomnia = project.join(".insomnia"); let yoi = project.join(".yoi");
std::fs::create_dir_all(&insomnia).unwrap(); std::fs::create_dir_all(&yoi).unwrap();
std::fs::write( std::fs::write(
insomnia.join("profiles.toml"), yoi.join("profiles.toml"),
r#" r#"
default = "coder" default = "coder"
[profile.coder] [profile.coder]
@ -695,7 +695,7 @@ description = "Project coder"
assert_eq!(choices[0].selector.as_deref(), Some("builtin:default")); assert_eq!(choices[0].selector.as_deref(), Some("builtin:default"));
assert_eq!( assert_eq!(
choices[0].label, choices[0].label,
"builtin:default — Bundled default Insomnia coding profile" "builtin:default — Bundled default Yoi coding profile"
); );
assert_eq!(default_index, 1); assert_eq!(default_index, 1);
assert_eq!(choices[1].selector.as_deref(), Some("project:coder")); assert_eq!(choices[1].selector.as_deref(), Some("project:coder"));

View File

@ -176,7 +176,7 @@ mod tests {
fn workflow_lint_accepts_valid_file() { fn workflow_lint_accepts_valid_file() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
write( write(
&dir.path().join(".insomnia/knowledge/policy.md"), &dir.path().join(".yoi/knowledge/policy.md"),
"---\ndescription: p\n---\nbody", "---\ndescription: p\n---\nbody",
); );
let wf = "---\ndescription: run\nrequires: [policy]\n---\nbody"; let wf = "---\ndescription: run\nrequires: [policy]\n---\nbody";

View File

@ -6,7 +6,7 @@ use manifest::{Permission, ScopeRule};
use memory::WorkspaceLayout; use memory::WorkspaceLayout;
/// Build deny rules that strip Write permission from /// Build deny rules that strip Write permission from
/// `<workspace>/.insomnia/workflow/` for generic CRUD tools. /// `<workspace>/.yoi/workflow/` for generic CRUD tools.
pub fn deny_write_rules(layout: &WorkspaceLayout) -> Vec<ScopeRule> { pub fn deny_write_rules(layout: &WorkspaceLayout) -> Vec<ScopeRule> {
vec![deny_write(layout.workflow_dir().as_path())] vec![deny_write(layout.workflow_dir().as_path())]
} }
@ -29,7 +29,7 @@ mod tests {
let layout = WorkspaceLayout::new(PathBuf::from("/ws")); let layout = WorkspaceLayout::new(PathBuf::from("/ws"));
let rules = deny_write_rules(&layout); let rules = deny_write_rules(&layout);
assert_eq!(rules.len(), 1); assert_eq!(rules.len(), 1);
assert_eq!(rules[0].target, PathBuf::from("/ws/.insomnia/workflow")); assert_eq!(rules[0].target, PathBuf::from("/ws/.yoi/workflow"));
assert_eq!(rules[0].permission, Permission::Write); assert_eq!(rules[0].permission, Permission::Write);
assert!(rules[0].recursive); assert!(rules[0].recursive);
} }

View File

@ -3,13 +3,13 @@
//! Skills follow the [agentskills.io](https://agentskills.io/specification) //! Skills follow the [agentskills.io](https://agentskills.io/specification)
//! spec: a directory `<root>/<name>/` containing `SKILL.md` (YAML frontmatter //! spec: a directory `<root>/<name>/` containing `SKILL.md` (YAML frontmatter
//! + Markdown body) and optional `scripts/` / `references/` / `assets/` //! + Markdown body) and optional `scripts/` / `references/` / `assets/`
//! subdirectories. The body is procedural agent guidance; insomnia ingests //! subdirectories. The body is procedural agent guidance; yoi ingests
//! it as a Workflow so `/<name>` resolves to it just like an internal //! it as a Workflow so `/<name>` resolves to it just like an internal
//! Workflow. //! Workflow.
//! //!
//! Parsing is intentionally lenient at the directory-scan level — one //! Parsing is intentionally lenient at the directory-scan level — one
//! malformed SKILL.md emits `tracing::warn!` and is skipped, leaving sibling //! malformed SKILL.md emits `tracing::warn!` and is skipped, leaving sibling
//! skills loadable. Internal Workflows (`.insomnia/workflow/<slug>.md`) keep //! skills loadable. Internal Workflows (`.yoi/workflow/<slug>.md`) keep
//! their hard-error semantics. //! their hard-error semantics.
use std::io; use std::io;
@ -30,7 +30,7 @@ pub const SKILL_FILENAME: &str = "SKILL.md";
/// SKILL.md frontmatter as defined by the agent-skills spec. /// SKILL.md frontmatter as defined by the agent-skills spec.
/// ///
/// Fields beyond `name` / `description` are accepted to be spec-compatible /// Fields beyond `name` / `description` are accepted to be spec-compatible
/// but not used by insomnia today: `license`, `compatibility`, and /// but not used by yoi today: `license`, `compatibility`, and
/// `metadata` are documentary, while `allowed-tools` is recognised and /// `metadata` are documentary, while `allowed-tools` is recognised and
/// emits a warning until [`permission-extension-point.md`] lands. /// emits a warning until [`permission-extension-point.md`] lands.
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]

View File

@ -1,6 +1,6 @@
//! Workflow loader and registry. //! Workflow loader and registry.
//! //!
//! Workflows live under `<workspace>/.insomnia/workflow/<slug>.md`. They are //! Workflows live under `<workspace>/.yoi/workflow/<slug>.md`. They are
//! human-authored Markdown documents with YAML frontmatter. The loader is //! human-authored Markdown documents with YAML frontmatter. The loader is
//! intentionally strict about malformed records because Pod startup should //! intentionally strict about malformed records because Pod startup should
//! fail rather than silently ignoring a broken procedural instruction. //! fail rather than silently ignoring a broken procedural instruction.
@ -27,7 +27,7 @@ pub const WORKFLOW_DESCRIPTION_HARD_CAP: usize = 1024;
/// win over external skills. /// win over external skills.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkflowSource { pub enum WorkflowSource {
/// `<workspace>/.insomnia/workflow/<slug>.md`. Authored in-tree by /// `<workspace>/.yoi/workflow/<slug>.md`. Authored in-tree by
/// the project. /// the project.
WorkspaceWorkflow, WorkspaceWorkflow,
/// SKILL.md ingested from a `[skills] directories` entry in the /// SKILL.md ingested from a `[skills] directories` entry in the
@ -316,13 +316,13 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout) { fn setup() -> (TempDir, WorkspaceLayout) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join(".insomnia/workflow")).unwrap(); std::fs::create_dir_all(dir.path().join(".yoi/workflow")).unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
(dir, layout) (dir, layout)
} }
fn write_workflow(root: &Path, slug: &str, frontmatter: &str, body: &str) { fn write_workflow(root: &Path, slug: &str, frontmatter: &str, body: &str) {
let path = root.join(".insomnia/workflow").join(format!("{slug}.md")); let path = root.join(".yoi/workflow").join(format!("{slug}.md"));
std::fs::write(path, format!("---\n{frontmatter}\n---\n{body}")).unwrap(); std::fs::write(path, format!("---\n{frontmatter}\n---\n{body}")).unwrap();
} }
@ -380,12 +380,12 @@ mod tests {
#[test] #[test]
fn workflow_under_memory_is_ignored() { fn workflow_under_memory_is_ignored() {
// The legacy `.insomnia/memory/workflow/` location is no longer // The legacy `.yoi/memory/workflow/` location is no longer
// a Workflow source. Files placed there must be ignored (the // a Workflow source. Files placed there must be ignored (the
// loader is rooted at `.insomnia/workflow/` only). // loader is rooted at `.yoi/workflow/` only).
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
let legacy = dir.path().join(".insomnia/memory/workflow"); let legacy = dir.path().join(".yoi/memory/workflow");
std::fs::create_dir_all(&legacy).unwrap(); std::fs::create_dir_all(&legacy).unwrap();
std::fs::write( std::fs::write(
legacy.join("ghost.md"), legacy.join("ghost.md"),
@ -509,7 +509,7 @@ mod tests {
let s = ShadowedSkill { let s = ShadowedSkill {
slug: Slug::parse("x").unwrap(), slug: Slug::parse("x").unwrap(),
kept_source: WorkflowSource::WorkspaceWorkflow, kept_source: WorkflowSource::WorkspaceWorkflow,
kept_path: std::path::PathBuf::from("/ws/.insomnia/workflow/x.md"), kept_path: std::path::PathBuf::from("/ws/.yoi/workflow/x.md"),
shadowed_source: WorkflowSource::Skill { shadowed_source: WorkflowSource::Skill {
dir: std::path::PathBuf::from("/skills"), dir: std::path::PathBuf::from("/skills"),
}, },

View File

@ -1,5 +1,5 @@
[package] [package]
name = "insomnia" name = "yoi"
version = "0.1.0" version = "0.1.0"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@ -7,6 +7,7 @@ license.workspace = true
[dependencies] [dependencies]
client = { workspace = true } client = { workspace = true }
memory = { workspace = true } memory = { workspace = true }
manifest = { workspace = true }
pod = { workspace = true } pod = { workspace = true }
session-store = { workspace = true } session-store = { workspace = true }
tui = { workspace = true } tui = { workspace = true }

View File

@ -35,8 +35,8 @@ async fn main() -> ExitCode {
let mode = match parse_args() { let mode = match parse_args() {
Ok(mode) => mode, Ok(mode) => mode,
Err(e) => { Err(e) => {
eprintln!("insomnia: {e}"); eprintln!("yoi: {e}");
eprintln!("try `insomnia --help` for usage."); eprintln!("try `yoi --help` for usage.");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };
@ -54,17 +54,17 @@ async fn main() -> ExitCode {
Ok(LintStatus::Clean) => ExitCode::SUCCESS, Ok(LintStatus::Clean) => ExitCode::SUCCESS,
Ok(LintStatus::Failed) => ExitCode::FAILURE, Ok(LintStatus::Failed) => ExitCode::FAILURE,
Err(e) => { Err(e) => {
eprintln!("insomnia memory lint: {e}"); eprintln!("yoi memory lint: {e}");
ExitCode::FAILURE ExitCode::FAILURE
} }
}, },
Mode::PodRuntime(args) => pod::entrypoint::run_cli_from("insomnia pod", args).await, Mode::PodRuntime(args) => pod::entrypoint::run_cli_from("yoi pod", args).await,
Mode::Keys => tui::keys::launch().await, Mode::Keys => tui::keys::launch().await,
Mode::Tui(mode) => { Mode::Tui(mode) => {
let runtime_command = match PodRuntimeCommand::resolve() { let runtime_command = match PodRuntimeCommand::resolve() {
Ok(command) => command, Ok(command) => command,
Err(e) => { Err(e) => {
eprintln!("insomnia: failed to resolve Pod runtime command: {e}"); eprintln!("yoi: failed to resolve Pod runtime command: {e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };
@ -100,7 +100,7 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
"pod" => return Ok(Mode::PodRuntime(args[1..].to_vec())), "pod" => return Ok(Mode::PodRuntime(args[1..].to_vec())),
"keys" => { "keys" => {
if args.len() != 1 { if args.len() != 1 {
return Err(ParseError("insomnia keys does not accept arguments".into())); return Err(ParseError("yoi keys does not accept arguments".into()));
} }
return Ok(Mode::Keys); return Ok(Mode::Keys);
} }
@ -322,13 +322,13 @@ fn parse_session_id(value: &str) -> Result<SegmentId, ParseError> {
fn print_help() { fn print_help() {
println!( println!(
"insomnia\n\nUsage:\n insomnia [OPTIONS] [POD_NAME]\n insomnia keys\n insomnia pod [POD_OPTIONS]\n insomnia memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --multi Open the multi-Pod dashboard\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Start a fresh Pod from a profile\n -h, --help Print help\n" "yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi keys\n yoi pod [POD_OPTIONS]\n yoi memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --multi Open the multi-Pod dashboard\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Start a fresh Pod from a profile\n -h, --help Print help\n"
); );
} }
fn print_memory_lint_help() { fn print_memory_lint_help() {
println!( println!(
"insomnia memory lint\n\nUsage:\n insomnia memory lint [OPTIONS]\n\nOptions:\n --workspace <PATH> Workspace root to lint (defaults to cwd)\n --json Emit a JSON report\n --warnings-as-errors Return failure when warnings are present\n -h, --help Print help\n" "yoi memory lint\n\nUsage:\n yoi memory lint [OPTIONS]\n\nOptions:\n --workspace <PATH> Workspace root to lint (defaults to cwd)\n --json Emit a JSON report\n --warnings-as-errors Return failure when warnings are present\n -h, --help Print help\n"
); );
} }
@ -524,7 +524,7 @@ mod tests {
"--profile".to_string(), "--profile".to_string(),
"p.lua".to_string(), "p.lua".to_string(),
"--socket".to_string(), "--socket".to_string(),
"/tmp/insomnia/sock".to_string(), "/tmp/yoi/sock".to_string(),
], ],
"--profile can only be used for fresh spawn", "--profile can only be used for fresh spawn",
), ),

View File

@ -416,19 +416,16 @@ mod tests {
fn lints_only_workspace_memory_and_knowledge_records() { fn lints_only_workspace_memory_and_knowledge_records() {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let root = dir.path(); let root = dir.path();
write(&root.join(".insomnia/memory/summary.md"), valid_summary()); write(&root.join(".yoi/memory/summary.md"), valid_summary());
write( write(
&root.join(".insomnia/memory/requests/request-one.md"), &root.join(".yoi/memory/requests/request-one.md"),
valid_request(), valid_request(),
); );
write( write(
&root.join(".insomnia/memory/_logs/ignored.md"), &root.join(".yoi/memory/_logs/ignored.md"),
"not frontmatter",
);
write(
&root.join(".insomnia/workflow/ignored.md"),
"not frontmatter", "not frontmatter",
); );
write(&root.join(".yoi/workflow/ignored.md"), "not frontmatter");
let report = lint_workspace(root).unwrap(); let report = lint_workspace(root).unwrap();
assert_eq!( assert_eq!(
@ -438,8 +435,8 @@ mod tests {
.map(|file| file.path.as_str()) .map(|file| file.path.as_str())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![ vec![
".insomnia/memory/requests/request-one.md", ".yoi/memory/requests/request-one.md",
".insomnia/memory/summary.md", ".yoi/memory/summary.md",
] ]
); );
assert_eq!(report.counts.files, 2); assert_eq!(report.counts.files, 2);
@ -450,10 +447,7 @@ mod tests {
fn invalid_records_count_as_lint_failures() { fn invalid_records_count_as_lint_failures() {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let root = dir.path(); let root = dir.path();
write( write(&root.join(".yoi/memory/summary.md"), "missing frontmatter");
&root.join(".insomnia/memory/summary.md"),
"missing frontmatter",
);
let report = lint_workspace(root).unwrap(); let report = lint_workspace(root).unwrap();
assert_eq!(report.counts.files, 1); assert_eq!(report.counts.files, 1);
@ -467,7 +461,7 @@ mod tests {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let root = dir.path(); let root = dir.path();
write( write(
&root.join(".insomnia/memory/requests/large-record.md"), &root.join(".yoi/memory/requests/large-record.md"),
&warning_request(), &warning_request(),
); );
@ -492,7 +486,7 @@ mod tests {
fn json_output_is_machine_readable() { fn json_output_is_machine_readable() {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let root = dir.path(); let root = dir.path();
write(&root.join(".insomnia/memory/summary.md"), valid_summary()); write(&root.join(".yoi/memory/summary.md"), valid_summary());
let mut output = Vec::new(); let mut output = Vec::new();
let status = run_with_writer( let status = run_with_writer(
@ -509,7 +503,7 @@ mod tests {
let parsed: Value = serde_json::from_slice(&output).unwrap(); let parsed: Value = serde_json::from_slice(&output).unwrap();
assert_eq!(parsed["workspace"], root.display().to_string()); assert_eq!(parsed["workspace"], root.display().to_string());
assert_eq!(parsed["counts"]["files"], 1); assert_eq!(parsed["counts"]["files"], 1);
assert_eq!(parsed["files"][0]["path"], ".insomnia/memory/summary.md"); assert_eq!(parsed["files"][0]["path"], ".yoi/memory/summary.md");
assert!(parsed["files"][0]["errors"].as_array().unwrap().is_empty()); assert!(parsed["files"][0]["errors"].as_array().unwrap().is_empty());
} }
} }

View File

@ -1,4 +1,4 @@
# Insomnia アーキテクチャ # Yoi アーキテクチャ
## プロジェクトの目的 ## プロジェクトの目的
@ -12,7 +12,7 @@
### 宣言した層が解決する ### 宣言した層が解決する
ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `[model] ref = "anthropic/claude-sonnet-4-6"`(あるいは `scheme = "anthropic"` + `model_id = ...` の inline 形式)と書いた以上、`ModelManifest` → `LlmClient` の変換は insomnia 側(`crates/provider`)が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。 ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `[model] ref = "anthropic/claude-sonnet-4-6"`(あるいは `scheme = "anthropic"` + `model_id = ...` の inline 形式)と書いた以上、`ModelManifest` → `LlmClient` の変換は yoi 側(`crates/provider`)が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。
### 概念の追加は不在が問題になってから ### 概念の追加は不在が問題になってから
@ -20,7 +20,7 @@
### 最小の構造化で最大の自由度 ### 最小の構造化で最大の自由度
insomnia は環境再現・コンテナ管理・VCS 統合などを自身の責務としない。Pod が動くホストの fs 上で活動する主体を提供し、それにコンテキストを与えてスポーンさせられる仕組みを付与する。構築する環境はユーザー次第。 yoi は環境再現・コンテナ管理・VCS 統合などを自身の責務としない。Pod が動くホストの fs 上で活動する主体を提供し、それにコンテキストを与えてスポーンさせられる仕組みを付与する。構築する環境はユーザー次第。
## Pod ## Pod
@ -88,7 +88,7 @@ name = "agent"
ref = "anthropic/claude-sonnet-4-6" ref = "anthropic/claude-sonnet-4-6"
[worker] [worker]
instruction = "$insomnia/default" instruction = "$yoi/default"
max_tokens = 4096 max_tokens = 4096
temperature = 0.3 temperature = 0.3
@ -103,7 +103,7 @@ permission = "write"
通常の Pod 起動は Lua profile discovery/default から `PodManifest` を生成する。bundled `builtin:default` が fallback default で、user/project `profiles.toml` は profile registry と default selection だけを担う。user/project `manifest.toml` の ambient cascade は通常起動では使わない。 通常の Pod 起動は Lua profile discovery/default から `PodManifest` を生成する。bundled `builtin:default` が fallback default で、user/project `profiles.toml` は profile registry と default selection だけを担う。user/project `manifest.toml` の ambient cascade は通常起動では使わない。
`insomnia pod --manifest <PATH>` は explicit one-file compatibility/debug input で、指定 TOML 1 枚だけに builtin defaults を merge し、`PodManifestConfig -> PodManifest` の required validation を通す。 `yoi pod --manifest <PATH>` は explicit one-file compatibility/debug input で、指定 TOML 1 枚だけに builtin defaults を merge し、`PodManifestConfig -> PodManifest` の required validation を通す。
`PodFactory` の user/project/overlay API は低レベル構成部品として残るが、CLI の通常起動 path では generic TOML overlay を公開しない。 `PodFactory` の user/project/overlay API は低レベル構成部品として残るが、CLI の通常起動 path では generic TOML overlay を公開しない。
@ -111,11 +111,11 @@ permission = "write"
`worker.instruction` はファイル参照。3 層の prefix addressing でプロンプト資産を解決: `worker.instruction` はファイル参照。3 層の prefix addressing でプロンプト資産を解決:
- `$insomnia/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み) - `$yoi/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み)
- `$user/...``<config_dir>/prompts/``manifest::paths` で解決) - `$user/...``<config_dir>/prompts/``manifest::paths` で解決)
- `$workspace/...``<project>/.insomnia/prompts/` - `$workspace/...``<project>/.yoi/prompts/`
テンプレートは minijinja で評価。`{% include "$insomnia/common/tool-usage" %}` のようにプロンプト間で参照可能prefix なしの include は現在のファイルからの相対解決)。 テンプレートは minijinja で評価。`{% include "$yoi/common/tool-usage" %}` のようにプロンプト間で参照可能prefix なしの include は現在のファイルからの相対解決)。
レンダリング結果の末尾に scope summary と AGENTS.mdあればがコード側で固定付加される。ユーザーテンプレートからはこれらに触れない。 レンダリング結果の末尾に scope summary と AGENTS.mdあればがコード側で固定付加される。ユーザーテンプレートからはこれらに触れない。

View File

@ -115,7 +115,7 @@ If `Yoi` is adopted, the rename should cover at least:
- docs, reports, AGENTS instructions, tickets, and release material; - docs, reports, AGENTS instructions, tickets, and release material;
- socket/runtime path labels and diagnostics where user-visible. - socket/runtime path labels and diagnostics where user-visible.
Compatibility aliases should be considered separately, but the public identity should not preserve `insomnia` longer than necessary. The public identity should move cleanly to Yoi; do not preserve `insomnia` through compatibility aliases.
## Adoption checks ## Adoption checks

View File

@ -1,8 +1,8 @@
# 環境変数ポリシー # 環境変数ポリシー
INSOMNIA では、プロセス境界で本当に必要な場合を除き、環境変数の利用を避ける。新しい ambient な入力を増やすより、明示的な profile / manifest / config file / typed secret reference / CLI argument を優先する。 Yoi では、プロセス境界で本当に必要な場合を除き、環境変数の利用を避ける。新しい ambient な入力を増やすより、明示的な profile / manifest / config file / typed secret reference / CLI argument を優先する。
それでも、path discovery、runtime directory、外部 provider の credential 慣習との移行互換のために、一部の環境変数はまだサポートしている。この文書に載せた通常 runtime 用の環境変数は公開 surface として扱う。ただし、fallback 変数は独立した設定項目ではなく、対応する main key の解決順の一部として扱う。開発・テスト都合だけの環境変数は、通常ユーザー向け configuration として扱わない明確な escape hatch に限る。 それでも、path discovery、runtime directory、外部 provider の credential 慣習のために、一部の環境変数はまだサポートしている。この文書に載せた通常 runtime 用の環境変数は公開 surface として扱う。ただし、fallback 変数は独立した設定項目ではなく、対応する main key の解決順の一部として扱う。開発・テスト都合だけの環境変数は、通常ユーザー向け configuration として扱わない明確な escape hatch に限る。
## 原則 ## 原則
@ -19,10 +19,10 @@ Path 系の環境変数は論理的な key ごとに立項する。`XDG_*` や `
| 論理 key | Main env | Fallback / 解決順 | 用途と位置付け | | 論理 key | Main env | Fallback / 解決順 | 用途と位置付け |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `home` | `INSOMNIA_HOME` | なし | config / data / runtime をまとめて sandbox する root override。設定を細かく分けるより、test や isolated run ではまずこれを使う。 | | `home` | `YOI_HOME` | なし | config / data / runtime をまとめて sandbox する root override。設定を細かく分けるより、test や isolated run ではまずこれを使う。 |
| `config_dir` | `INSOMNIA_CONFIG_DIR` | `$INSOMNIA_HOME/config``$XDG_CONFIG_HOME/insomnia``$HOME/.config/insomnia` | 人が書く設定・override の置き場。`profiles.toml`、prompt override、model/provider override など。 | | `config_dir` | `YOI_CONFIG_DIR` | `$YOI_HOME/config``$XDG_CONFIG_HOME/yoi``$HOME/.config/yoi` | 人が書く設定・override の置き場。`profiles.toml`、prompt override、model/provider override など。 |
| `data_dir` | `INSOMNIA_DATA_DIR` | `$INSOMNIA_HOME``$HOME/.insomnia` | プログラムが書く永続データの置き場。session log、Pod metadata など、再起動後も restore / replay の根拠になるもの。通常ユーザー向けの primary knob ではなく、migration、test、isolated data store 用の advanced override。 | | `data_dir` | `YOI_DATA_DIR` | `$YOI_HOME``$HOME/.yoi` | プログラムが書く永続データの置き場。session log、Pod metadata など、再起動後も restore / replay の根拠になるもの。通常ユーザー向けの primary knob ではなく、test や isolated data store 用の advanced override。 |
| `runtime_dir` | `INSOMNIA_RUNTIME_DIR` | `$INSOMNIA_HOME/run``$XDG_RUNTIME_DIR/insomnia``$HOME/.insomnia/run` | socket、pid/status file、live registry mirror など、再起動で捨ててよい runtime state の置き場。 | | `runtime_dir` | `YOI_RUNTIME_DIR` | `$YOI_HOME/run``$XDG_RUNTIME_DIR/yoi``$HOME/.yoi/run` | socket、pid/status file、live registry mirror など、再起動で捨ててよい runtime state の置き場。 |
空の path 環境変数は、`manifest::paths` では原則として unset 相当に扱う。 空の path 環境変数は、`manifest::paths` では原則として unset 相当に扱う。
@ -36,11 +36,11 @@ Path 系の環境変数は論理的な key ごとに立項する。`XDG_*` や `
### Builtin assets と `config_dir` ### Builtin assets と `config_dir`
Builtin profiles and catalogs are embedded in the binary at build time. User/project-owned overrides remain under `config_dir` and project `.insomnia/` files such as `profiles.toml`; package runtime resource lookup is not a supported configuration surface. Builtin profiles and catalogs are embedded in the binary at build time. User/project-owned overrides remain under `config_dir` and project `.yoi/` files such as `profiles.toml`; package runtime resource lookup is not a supported configuration surface.
## Credential と外部 auth ## Credential と外部 auth
Provider API key と WebSearch credential は、通常の runtime では環境変数から読まない。`insomnia keys` で local secret store に論理 id を追加し、profile / manifest / provider catalog / web config がその id を明示的に参照する。 Provider API key と WebSearch credential は、通常の runtime では環境変数から読まない。`yoi keys` で local secret store に論理 id を追加し、profile / manifest / provider catalog / web config がその id を明示的に参照する。
```toml ```toml
[model] [model]
@ -71,7 +71,7 @@ On-disk store は `<data_dir>/secrets/store.json`。secret value は軽量な ob
| 変数 | Context | 備考 | | 変数 | Context | 備考 |
| --- | --- | --- | | --- | --- | --- |
| `INSOMNIA_POD_RUNTIME_COMMAND` | 開発中に起動中の `insomnia` binary が rebuild され、`std::env::current_exe()` が `target/debug/insomnia (deleted)` のような stale path を返す場合の Pod runtime executable override。 | Unset または empty の場合は既定どおり current executable に `pod` prefix argument を付けて起動する。Non-empty の場合は値を executable path としてそのまま使い、`pod` prefix argument は常に自動追加する。shell parsing や argument splitting は行わないため、値に flags や `pod` を含めない。 | | `YOI_POD_RUNTIME_COMMAND` | 開発中に起動中の `yoi` binary が rebuild され、`std::env::current_exe()` が `target/debug/yoi (deleted)` のような stale path を返す場合の Pod runtime executable override。 | Unset または empty の場合は既定どおり current executable に `pod` prefix argument を付けて起動する。Non-empty の場合は値を executable path としてそのまま使い、`pod` prefix argument は常に自動追加する。shell parsing や argument splitting は行わないため、値に flags や `pod` を含めない。 |
## Build / example variables ## Build / example variables
@ -83,7 +83,7 @@ On-disk store は `<data_dir>/secrets/store.json`。secret value は軽量な ob
| `PATH` | test / dev command lookup。 | helper executable を探す場合だけ使う。 | | `PATH` | test / dev command lookup。 | helper executable を探す場合だけ使う。 |
| `TMPDIR` | shell script / test。 | `tickets.sh` が temporary file に使う。 | | `TMPDIR` | shell script / test。 | `tickets.sh` が temporary file に使う。 |
| `RUST_LOG` | example / dev diagnostics。 | example CLI が tracing setup 経由で読む場合がある。 | | `RUST_LOG` | example / dev diagnostics。 | example CLI が tracing setup 経由で読む場合がある。 |
| `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` などの provider example vars | `llm-worker` や Pod example / fixture recorder。 | example code が `dotenv::dotenv().ok()` を呼ぶことがある。通常の `insomnia` runtime startup には適用されない。 | | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` などの provider example vars | `llm-worker` や Pod example / fixture recorder。 | example code が `dotenv::dotenv().ok()` を呼ぶことがある。通常の `yoi` runtime startup には適用されない。 |
## 整理方針 ## 整理方針

View File

@ -1,15 +1,15 @@
# Manifest profiles # Manifest profiles
Profiles are reusable Lua-authored recipes for generating an Insomnia runtime manifest. The Rust resolver evaluates a selected `.lua` profile in-process, validates that it is Profile-shaped rather than a complete Manifest, then binds runtime values such as Pod name and concrete scope to produce the persisted `PodManifest` snapshot. Profiles are reusable Lua-authored recipes for generating a Yoi runtime manifest. The Rust resolver evaluates a selected `.lua` profile in-process, validates that it is Profile-shaped rather than a complete Manifest, then binds runtime values such as Pod name and concrete scope to produce the persisted `PodManifest` snapshot.
Profiles are intentionally not authority-bearing manifests. `pod.name`, concrete `scope.allow` / `scope.deny`, runtime directories, sockets, active session state, and raw secret material do not belong in reusable profiles. Use `insomnia keys` to store provider/WebSearch credentials, then reference explicit secret ids such as `auth = { kind = "secret_ref", ref = "providers/anthropic/default" }` or `web.search.api_key_secret = "web/brave/default"`. Use `--manifest` when you need the explicit low-level complete Manifest escape hatch. Profiles are intentionally not authority-bearing manifests. `pod.name`, concrete `scope.allow` / `scope.deny`, runtime directories, sockets, active session state, and raw secret material do not belong in reusable profiles. Use `yoi keys` to store provider/WebSearch credentials, then reference explicit secret ids such as `auth = { kind = "secret_ref", ref = "providers/anthropic/default" }` or `web.search.api_key_secret = "web/brave/default"`. Use `--manifest` when you need the explicit low-level complete Manifest escape hatch.
## Minimal profile ## Minimal profile
```lua ```lua
local profile = require("insomnia.profile") local profile = require("yoi.profile")
local models = require("insomnia.models") local models = require("yoi.models")
local scope = require("insomnia.scope") local scope = require("yoi.scope")
return profile { return profile {
slug = "coder", slug = "coder",
@ -26,24 +26,24 @@ return profile {
Run an explicit path with: Run an explicit path with:
```sh ```sh
insomnia pod --profile ./coder.lua yoi pod --profile ./coder.lua
# or through the TUI fresh-spawn dialog # or through the TUI fresh-spawn dialog
insomnia --profile ./coder.lua yoi --profile ./coder.lua
``` ```
`--profile` accepts an explicit path, `path:<path>`, a discovered profile name, `default`, or a source-qualified name such as `project:coder`, `user:coder`, or `builtin:coder`. Path-like values containing `/`, starting with `.`, or ending in `.lua` are explicit paths. ``.nix` paths are no longer supported as profiles and fail with a diagnostic that points users at Lua profiles or `--manifest`. `--profile` accepts an explicit path, `path:<path>`, a discovered profile name, `default`, or a source-qualified name such as `project:coder`, `user:coder`, or `builtin:coder`. Path-like values containing `/`, starting with `.`, or ending in `.lua` are explicit paths. ``.nix` paths are no longer supported as profiles and fail with a diagnostic that points users at Lua profiles or `--manifest`.
`--profile` conflicts with `insomnia pod --manifest` and with restore/session/adopt modes. Use `--profile-pod-name <name>` when a launcher needs a creation-time Pod name override without invoking `--pod` restore semantics. Profile evaluation is a creation-time path; Pod resume restores saved Pod state/resolved snapshots rather than re-evaluating the profile source. `--profile` conflicts with `yoi pod --manifest` and with restore/session/adopt modes. Use `--profile-pod-name <name>` when a launcher needs a creation-time Pod name override without invoking `--pod` restore semantics. Profile evaluation is a creation-time path; Pod resume restores saved Pod state/resolved snapshots rather than re-evaluating the profile source.
## Controlled Lua environment ## Controlled Lua environment
Profiles run in a restricted Lua VM. Host virtual modules are available through controlled `require`: Profiles run in a restricted Lua VM. Host virtual modules are available through controlled `require`:
- `require("insomnia")` - `require("yoi")`
- `require("insomnia.profile")` - `require("yoi.profile")`
- `require("insomnia.models")` - `require("yoi.models")`
- `require("insomnia.compact")` - `require("yoi.compact")`
- `require("insomnia.scope")` - `require("yoi.scope")`
Profile-local modules may be required by dotted names such as `require("shared")` or `require("shared.models")`; those resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, `package`, `dofile`, and `loadfile` are unavailable by default. Profile-local modules may be required by dotted names such as `require("shared")` or `require("shared.models")`; those resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, `package`, `dofile`, and `loadfile` are unavailable by default.
@ -51,7 +51,7 @@ Profile-local modules may be required by dotted names such as `require("shared")
Profile discovery is separate from runtime manifest merging. User/project `profiles.toml` files may declare profile registry metadata, but those files are application/project UX configuration and are not merged into the selected profile artifact. Profile discovery is separate from runtime manifest merging. User/project `profiles.toml` files may declare profile registry metadata, but those files are application/project UX configuration and are not merged into the selected profile artifact.
Example project config at `.insomnia/profiles.toml`: Example project config at `.yoi/profiles.toml`:
```toml ```toml
default = "coder" default = "coder"
@ -69,19 +69,19 @@ path = "profiles/coder.lua"
description = "Project coding assistant" description = "Project coding assistant"
``` ```
Relative registry paths are resolved against the `profiles.toml` file that declares them. Discovery checks bundled builtin profiles, then the user registry at `<config_dir>/profiles.toml`, then the nearest project registry at `.insomnia/profiles.toml`. The bundled `builtin:default` profile is the fallback default when no user/project registry declares another default. Later defaults override earlier defaults, so a project default wins over a user default, and either wins over the builtin default. Unqualified defaults resolve within the declaring source by default. Unqualified ambiguous names fail closed: Relative registry paths are resolved against the `profiles.toml` file that declares them. Discovery checks bundled builtin profiles, then the user registry at `<config_dir>/profiles.toml`, then the nearest project registry at `.yoi/profiles.toml`. The bundled `builtin:default` profile is the fallback default when no user/project registry declares another default. Later defaults override earlier defaults, so a project default wins over a user default, and either wins over the builtin default. Unqualified defaults resolve within the declaring source by default. Unqualified ambiguous names fail closed:
```sh ```sh
insomnia --profile coder # fails if both user:coder and project:coder exist yoi --profile coder # fails if both user:coder and project:coder exist
insomnia --profile project:coder # source-qualified selection yoi --profile project:coder # source-qualified selection
insomnia --profile default # selected registry default yoi --profile default # selected registry default
``` ```
The fresh-spawn TUI also uses discovery. The new Pod dialog defaults to the selected registry default, normally `builtin:default` unless a user/project registry overrides it. `Tab`/`Down` cycles forward through discovered profiles and `Shift-Tab`/`Up` cycles backward; there is no ambient manifest-cascade opt-out. Passing `insomnia --profile <selector>` opens the same new Pod dialog with that selector selected and leaves Pod-name editing unchanged. The fresh-spawn TUI also uses discovery. The new Pod dialog defaults to the selected registry default, normally `builtin:default` unless a user/project registry overrides it. `Tab`/`Down` cycles forward through discovered profiles and `Shift-Tab`/`Up` cycles backward; there is no ambient manifest-cascade opt-out. Passing `yoi --profile <selector>` opens the same new Pod dialog with that selector selected and leaves Pod-name editing unchanged.
## One-file manifests ## One-file manifests
`insomnia pod --manifest <PATH>` remains as an explicit compatibility/debug path. It reads exactly that TOML file, resolves relative paths against the file's parent directory, merges builtin defaults, and validates through the same `PodManifestConfig -> PodManifest` boundary as profile artifacts. It does not load user or project `manifest.toml` files and conflicts with `--profile`. `yoi pod --manifest <PATH>` remains as an explicit compatibility/debug path. It reads exactly that TOML file, resolves relative paths against the file's parent directory, merges builtin defaults, and validates through the same `PodManifestConfig -> PodManifest` boundary as profile artifacts. It does not load user or project `manifest.toml` files and conflicts with `--profile`.
Ambient user/project `manifest.toml` cascade startup has been removed. Normal fresh spawns use profile discovery/default selection, with `profiles.toml` acting only as a profile registry/default selector. Ambient user/project `manifest.toml` cascade startup has been removed. Normal fresh spawns use profile discovery/default selection, with `profiles.toml` acting only as a profile registry/default selector.
@ -89,4 +89,4 @@ Ambient user/project `manifest.toml` cascade startup has been removed. Normal fr
A Lua profile should return either `profile { ... }` or a plain table containing Profile fields. The resolver converts reusable fields such as `model`, `worker`, `compaction`, `memory`, `web`, `permissions`, `session`, and scope intent into a concrete Manifest. Runtime Pod name and concrete scope authority are supplied by launch context, then the resolved Manifest snapshot is persisted for restore. A Lua profile should return either `profile { ... }` or a plain table containing Profile fields. The resolver converts reusable fields such as `model`, `worker`, `compaction`, `memory`, `web`, `permissions`, `session`, and scope intent into a concrete Manifest. Runtime Pod name and concrete scope authority are supplied by launch context, then the resolved Manifest snapshot is persisted for restore.
Profile and one-file manifest CLI paths currently use builtin prompt assets only. `$insomnia/...` instruction refs work; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery. Profile and one-file manifest CLI paths currently use builtin prompt assets only. `$yoi/...` instruction refs work; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery.

View File

@ -5,7 +5,7 @@
# #
# このファイル形式は低レベル runtime manifest。通常起動は profile discovery/default # このファイル形式は低レベル runtime manifest。通常起動は profile discovery/default
# (`profiles.toml` と bundled builtin profile) から manifest を生成する。 # (`profiles.toml` と bundled builtin profile) から manifest を生成する。
# `insomnia pod --manifest <path>` の one-file compatibility/debug mode では、 # `yoi pod --manifest <path>` の one-file compatibility/debug mode では、
# 指定した TOML 1 枚に builtin defaults を merge し、required validation を行う。 # 指定した TOML 1 枚に builtin defaults を merge し、required validation を行う。
# user/project `manifest.toml` を暗黙に merge する通常起動 cascade は使わない。 # user/project `manifest.toml` を暗黙に merge する通常起動 cascade は使わない。
# #
@ -65,7 +65,7 @@ ref = "anthropic/claude-sonnet-4-6"
# 任意 (ref 未指定時は実質必須)。デフォルト: なし。 # 任意 (ref 未指定時は実質必須)。デフォルト: なし。
# kind の値: "none" | "secret_ref" | "api_key" | "codex_oauth" # kind の値: "none" | "secret_ref" | "api_key" | "codex_oauth"
# - "none" … 認証不要 (ローカル Ollama 等) # - "none" … 認証不要 (ローカル Ollama 等)
# - "secret_ref" … `insomnia keys` の local secret store から key を読む。 # - "secret_ref" … `yoi keys` の local secret store から key を読む。
# `ref` はユーザー設定が明示的に選ぶ論理 secret id。 # `ref` はユーザー設定が明示的に選ぶ論理 secret id。
# store は id -> value のみを持ち、provider 種別を解釈しない。 # store は id -> value のみを持ち、provider 種別を解釈しない。
# - "api_key" … 明示ファイルから key を読む低レベル形式。通常は # - "api_key" … 明示ファイルから key を読む低レベル形式。通常は
@ -95,10 +95,10 @@ ref = "anthropic/claude-sonnet-4-6"
# ワーカーの生成パラメータ等。セクション自体省略可 (全フィールド任意)。 # ワーカーの生成パラメータ等。セクション自体省略可 (全フィールド任意)。
[worker] [worker]
# 任意。デフォルト: "$insomnia/default" (`defaults::DEFAULT_INSTRUCTION`)。 # 任意。デフォルト: "$yoi/default" (`defaults::DEFAULT_INSTRUCTION`)。
# システムプロンプト本体の `PromptLoader` 参照。 # システムプロンプト本体の `PromptLoader` 参照。
# プレフィクス: "$insomnia/..." | "$user/..." | "$workspace/..." # プレフィクス: "$yoi/..." | "$user/..." | "$workspace/..."
# instruction = "$insomnia/default" # instruction = "$yoi/default"
# 任意。デフォルト: なし (プロバイダ任せ)。 # 任意。デフォルト: なし (プロバイダ任せ)。
# 1 レスポンスあたりの出力 token 上限。 # 1 レスポンスあたりの出力 token 上限。

View File

@ -2,7 +2,7 @@
## 目的 ## 目的
AI maintainer は、insomnia リポジトリの開発を継続的に進めるための orchestration role である。単発の `/auto-maintain` より広く、設計相談、work item 整理、実装委譲、レビュー、運用課題の記録、改善提案を一つの maintainer loop として扱う。 AI maintainer は、yoi リポジトリの開発を継続的に進めるための orchestration role である。単発の `/auto-maintain` より広く、設計相談、work item 整理、実装委譲、レビュー、運用課題の記録、改善提案を一つの maintainer loop として扱う。
`/auto-maintain` はこの設計の限定実行形であり、`tickets.sh` / `work-items/` から小さな実装作業を選んで実装・レビューを orchestration する Workflow に留まる。 `/auto-maintain` はこの設計の限定実行形であり、`tickets.sh` / `work-items/` から小さな実装作業を選んで実装・レビューを orchestration する Workflow に留まる。
@ -16,7 +16,7 @@ AI maintainer は、insomnia リポジトリの開発を継続的に進めるた
- `resolution.md` は close 時の完了記録 - `resolution.md` は close 時の完了記録
- 時系列と状態遷移の最終根拠は git history - 時系列と状態遷移の最終根拠は git history
- `docs/report/` は観測・所感・改善候補の記録であり、最新仕様の authority ではない - `docs/report/` は観測・所感・改善候補の記録であり、最新仕様の authority ではない
- `.insomnia/memory` は個人/生成 state であり、project record の正本ではない - `.yoi/memory` は個人/生成 state であり、project record の正本ではない
AI maintainer は project record を勝手に膨らませない。明確な実装単位は work item 化し、小粒な所見は `KNOWN_ISSUES.md`、ドッグフーディング上の障壁やツール問題は `docs/report/` に記録する。 AI maintainer は project record を勝手に膨らませない。明確な実装単位は work item 化し、小粒な所見は `KNOWN_ISSUES.md`、ドッグフーディング上の障壁やツール問題は `docs/report/` に記録する。
@ -44,7 +44,7 @@ Maintainer Pod は「便利だから」project record を書き換えない。
制約: 制約:
- 指定 scope 外を編集しない - 指定 scope 外を編集しない
- `.insomnia` や main workspace の control-plane record を勝手に編集しない - `.yoi` や main workspace の control-plane record を勝手に編集しない
- work item / review / close は maintainer の責務として扱う - work item / review / close は maintainer の責務として扱う
- 実装報告には変更点、検証、未解決点を含める - 実装報告には変更点、検証、未解決点を含める
@ -185,8 +185,8 @@ AI maintainer は以下で人間に戻す。
## Reports / Knowledge / Memory ## Reports / Knowledge / Memory
- `docs/report/`: ドッグフーディングで感じた障壁、改善案、ツール問題の記録。明確な作業単位になったら work item 化する - `docs/report/`: ドッグフーディングで感じた障壁、改善案、ツール問題の記録。明確な作業単位になったら work item 化する
- `.insomnia/knowledge`: curated project knowledge。正本ではなく補助 context - `.yoi/knowledge`: curated project knowledge。正本ではなく補助 context
- `.insomnia/memory`: generated/personal state。project record の代替にしない - `.yoi/memory`: generated/personal state。project record の代替にしない
- `KNOWN_ISSUES.md`: ticket 化するほどではないが、次に近所を触る時に拾いたい小粒所見 - `KNOWN_ISSUES.md`: ticket 化するほどではないが、次に近所を触る時に拾いたい小粒所見
## Future extension points ## Future extension points

View File

@ -2,7 +2,7 @@
## Context ## Context
INSOMNIA が利用する LLM プロバイダとその認証方式を決める。従量 API 課金の心理的負担を避け、定額サブスク枠ChatGPT Codex / Ollama Cloudを活かせる構成にする。 Yoi が利用する LLM プロバイダとその認証方式を決める。従量 API 課金の心理的負担を避け、定額サブスク枠ChatGPT Codex / Ollama Cloudを活かせる構成にする。
詳細な現状調査は `docs/ref/llm-provider-landscape.md``docs/ref/llm-pricing-2026-04.md` を参照。 詳細な現状調査は `docs/ref/llm-provider-landscape.md``docs/ref/llm-pricing-2026-04.md` を参照。
@ -46,13 +46,13 @@ Ollama は独自 scheme を作らず `scheme/anthropic` を base_url 差し替
- 認証ストアを読むアダプタ(`~/.codex/auth.json` 等)は **llm-worker 直下に置かず上位層に配置**。llm-worker は低レベル基盤に留める方針(`feedback_llm_worker_scope.md`)と整合 - 認証ストアを読むアダプタ(`~/.codex/auth.json` 等)は **llm-worker 直下に置かず上位層に配置**。llm-worker は低レベル基盤に留める方針(`feedback_llm_worker_scope.md`)と整合
- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠はモデルカタログ(`resources/models/builtin.toml` + `<config_dir>/models.toml` の user override、`<config_dir>` は `manifest::paths` で解決)で宣言 - モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠はモデルカタログ(`resources/models/builtin.toml` + `<config_dir>/models.toml` の user override、`<config_dir>` は `manifest::paths` で解決)で宣言
- UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる - UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる
- **`ollama launch insomnia` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り - **`ollama launch yoi` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り
## 機能方針 ## 機能方針
### プロバイダ側の高次ツールは使用しない ### プロバイダ側の高次ツールは使用しない
`web_search` / `code_interpreter` / `computer_use` / Live Search 等はプロバイダ依存を避けるため不採用。insomnia の自前 Tool 層で統一。fallback や routing もワーカー側で管理するため OpenRouter の `transforms` / `provider routing` も使わない。 `web_search` / `code_interpreter` / `computer_use` / Live Search 等はプロバイダ依存を避けるため不採用。yoi の自前 Tool 層で統一。fallback や routing もワーカー側で管理するため OpenRouter の `transforms` / `provider routing` も使わない。
### 必須 capability ### 必須 capability

View File

@ -14,7 +14,7 @@
- ID: timestamp + slug 形式 - ID: timestamp + slug 形式
- Doctor: `./tickets.sh doctor` - Doctor: `./tickets.sh doctor`
`work-items/` は repo-managed な project coordination record であり、`.insomnia/memory` はその代替ではない。 `work-items/` は repo-managed な project coordination record であり、`.yoi/memory` はその代替ではない。
## 現在も残る設計余地 ## 現在も残る設計余地

View File

@ -10,7 +10,7 @@
- `memory/summary.md` は「Always-on サマリ」と設計されているが、通常 Pod の system prompt へ常駐注入されていない - `memory/summary.md` は「Always-on サマリ」と設計されているが、通常 Pod の system prompt へ常駐注入されていない
- consolidation は `KnowledgeCandidateReport::empty()` を受け取り、prompt 上も「候補レポートが空なら新規 Knowledge を作るな」としているため、Knowledge の cold-start が起きない - consolidation は `KnowledgeCandidateReport::empty()` を受け取り、prompt 上も「候補レポートが空なら新規 Knowledge を作るな」としているため、Knowledge の cold-start が起きない
- `decisions/*``requests/*` は記録としては残るが、description / resident injection を持たず、後続 turn で自然に読まれにくい - `decisions/*``requests/*` は記録としては残るが、description / resident injection を持たず、後続 turn で自然に読まれにくい
- bundled prompt に INSOMNIA 開発固有の ticket / TODO 運用を前提にした shadow 禁止が入り、一般ユーザー workspace の管理文脈を過剰に落とす可能性がある - bundled prompt に Yoi 開発固有の ticket / TODO 運用を前提にした shadow 禁止が入り、一般ユーザー workspace の管理文脈を過剰に落とす可能性がある
この状態で usage metrics を先に実装しても、「使われていない / 発見されない memory」を測るだけになり、Knowledge 化候補や保護閾値の信号が十分に育たない。先に memory が読まれ、Knowledge が最低限生まれる経路を作る。 この状態で usage metrics を先に実装しても、「使われていない / 発見されない memory」を測るだけになり、Knowledge 化候補や保護閾値の信号が十分に育たない。先に memory が読まれ、Knowledge が最低限生まれる経路を作る。
@ -78,7 +78,7 @@ Knowledge が空のまま固定される問題を避ける。metrics 実装前
### Problem ### Problem
bundled memory prompts は、INSOMNIA 自身の ticket / TODO / worktree 運用を一般ユーザーへ押し付けている。ticket はこのプロジェクトの管理手法であり、ユーザー workspace の正本や作業管理の形は project ごとに異なる。 bundled memory prompts は、Yoi 自身の ticket / TODO / worktree 運用を一般ユーザーへ押し付けている。ticket はこのプロジェクトの管理手法であり、ユーザー workspace の正本や作業管理の形は project ごとに異なる。
### Direction ### Direction
@ -89,7 +89,7 @@ Default prompt では特定の管理手法名を禁止対象として列挙し
- 既存の authoritative record を逐語的に mirror しない - 既存の authoritative record を逐語的に mirror しない
- ファイル操作ログや VCS 履歴そのものを memory に再保存しない - ファイル操作ログや VCS 履歴そのものを memory に再保存しない
- ただし、将来の作業判断に効く project-management 上の制約・優先順位理由・プロセス決定・ recurring pattern は抽象化して保存してよい - ただし、将来の作業判断に効く project-management 上の制約・優先順位理由・プロセス決定・ recurring pattern は抽象化して保存してよい
- INSOMNIA 自身の ticket shadow 回避は bundled default ではなく workspace / user prompt override で表現する - Yoi 自身の ticket shadow 回避は bundled default ではなく workspace / user prompt override で表現する
### Expected effect ### Expected effect
@ -181,7 +181,7 @@ cold-start gate を緩めると Knowledge が増えすぎる可能性がある
### Prompt override boundary ### Prompt override boundary
INSOMNIA 自身の ticket shadow 回避を完全に消すと、この repository の memory は作業ログ寄りに戻る可能性がある。これは bundled default ではなく workspace prompt override で解くべきで、default prompt の責務ではない。 Yoi 自身の ticket shadow 回避を完全に消すと、この repository の memory は作業ログ寄りに戻る可能性がある。これは bundled default ではなく workspace prompt override で解くべきで、default prompt の責務ではない。
### `#<slug>` history representation ### `#<slug>` history representation

View File

@ -2,7 +2,7 @@
## Context ## Context
INSOMNIA がユーザーのプロジェクトに対して提供するメモリ機構。プロジェクトの暗黙知蓄積と同じ失敗を繰り返さないための記憶が目的。エージェントに連続するアイデンティティや自己意識を持たせる方向は対象外。 Yoi がユーザーのプロジェクトに対して提供するメモリ機構。プロジェクトの暗黙知蓄積と同じ失敗を繰り返さないための記憶が目的。エージェントに連続するアイデンティティや自己意識を持たせる方向は対象外。
リサーチは `docs/ref/memory-systems.md`。前提として、**レポジトリがファイルシステム上にある**ケースで設計する(越境・バックエンド抽象は Scope 外)。 リサーチは `docs/ref/memory-systems.md`。前提として、**レポジトリがファイルシステム上にある**ケースで設計する(越境・バックエンド抽象は Scope 外)。
@ -14,7 +14,7 @@ Workflow`/<slug>` で呼び出される制約付き作業フロー)は別 p
### 記録対象の 4 種 ### 記録対象の 4 種
本ドキュメント以下のパスはすべて **`<workspace_root>/.insomnia/`** からの相対表記。`.insomnia/` は manifest / prompts と同じく workspace に紐付く insomnia コンテンツのルートで、memory もこの規約に従う。`workspace_root` 既定は Pod の pwd。 本ドキュメント以下のパスはすべて **`<workspace_root>/.yoi/`** からの相対表記。`.yoi/` は manifest / prompts と同じく workspace に紐付く yoi コンテンツのルートで、memory もこの規約に従う。`workspace_root` 既定は Pod の pwd。
| 種別 | パス | 備考 | | 種別 | パス | 備考 |
| ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------- | | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
@ -98,7 +98,7 @@ Linter ルールは 2 系統:
- Decisions / Requests: `created_at`, `updated_at`, `sources` - Decisions / Requests: `created_at`, `updated_at`, `sources`
- Knowledge: `kind`, `description`, `model_invokation`, `user_invocable`, `last_sources`, `created_at`, `updated_at` - Knowledge: `kind`, `description`, `model_invokation`, `user_invocable`, `last_sources`, `created_at`, `updated_at`
- Summary: `updated_at`optional: `last_rewritten_from_range` - Summary: `updated_at`optional: `last_rewritten_from_range`
- Workflow パス(`.insomnia/workflow/`への書き込み禁止sub-Worker context のみ、人間編集は除外) - Workflow パス(`.yoi/workflow/`への書き込み禁止sub-Worker context のみ、人間編集は除外)
- 同 slug での新規作成禁止(既存があれば update に切り替えるサイン) - 同 slug での新規作成禁止(既存があれば update に切り替えるサイン)
- `#<slug>` 参照が実在ファイルを指す - `#<slug>` 参照が実在ファイルを指す
- `replaced_by: <slug>` が実在 record を指す - `replaced_by: <slug>` が実在 record を指す

View File

@ -23,26 +23,26 @@ Pod が連携する等)では、マシン間のメッセージングが必要
## アドレッシング ## アドレッシング
Pod のネットワークアドレスは `insomnia.pod-name@host` の形式を**論理的な Pod のネットワークアドレスは `yoi.pod-name@host` の形式を**論理的な
宛先表記**として使う。実際の SSH 接続がこの文字列そのままで行えるかは 宛先表記**として使う。実際の SSH 接続がこの文字列そのままで行えるかは
transport 方式に依存する(後述)。 transport 方式に依存する(後述)。
- `insomnia` = SSH ユーザー名(固定) - `yoi` = SSH ユーザー名(固定)
- `host` = 相手マシンのホスト名 or IP - `host` = 相手マシンのホスト名 or IP
- `pod-name` = 送信先の Pod 名(相手マシン上のローカル workspace 内で一意) - `pod-name` = 送信先の Pod 名(相手マシン上のローカル workspace 内で一意)
推奨構文は **`insomnia@host:pod-name`**git 方式)。 推奨構文は **`yoi@host:pod-name`**git 方式)。
詳細は後述の「アドレッシング構文」を参照。 詳細は後述の「アドレッシング構文」を参照。
## アドレッシング構文 ## アドレッシング構文
論理的な宛先表記として **`insomnia@host:pod-name`** を推奨する。 論理的な宛先表記として **`yoi@host:pod-name`** を推奨する。
git の `git@github.com:user/repo` と同じ構文で: git の `git@github.com:user/repo` と同じ構文で:
- SSH ユーザーは `insomnia` 固定(動的ユーザー名が不要) - SSH ユーザーは `yoi` 固定(動的ユーザー名が不要)
- `:` 以降がルーティング情報Pod 名) - `:` 以降がルーティング情報Pod 名)
- クライアント側が `insomnia@host:pod-name` をパースし、 - クライアント側が `yoi@host:pod-name` をパースし、
`ssh insomnia@host "insomnia-route pod-name"` に変換する `ssh yoi@host "yoi-route pod-name"` に変換する
git がこの方式で `git-upload-pack user/repo` にルーティングしている git がこの方式で `git-upload-pack user/repo` にルーティングしている
のと同じ仕組み。OS レベルの設定NSS モジュール等)が一切不要で、 のと同じ仕組み。OS レベルの設定NSS モジュール等)が一切不要で、
@ -53,37 +53,37 @@ git がこの方式で `git-upload-pack user/repo` にルーティングして
### A. 単一ユーザー + コマンド引数 ### A. 単一ユーザー + コマンド引数
``` ```
ssh insomnia@host send pod-name "message" ssh yoi@host send pod-name "message"
``` ```
- 相手マシンにシステムユーザー `insomnia` を 1 つ作る - 相手マシンにシステムユーザー `yoi` を 1 つ作る
- `authorized_keys` に接続元 Pod の公開鍵を登録 - `authorized_keys` に接続元 Pod の公開鍵を登録
- ForceCommand または shell スクリプトが第一引数 (`send`) と - ForceCommand または shell スクリプトが第一引数 (`send`) と
第二引数 (`pod-name`) を解釈してローカル workspace のレジストリから 第二引数 (`pod-name`) を解釈してローカル workspace のレジストリから
Pod の socket を引き、メッセージをルーティング Pod の socket を引き、メッセージをルーティング
- **導入コスト最低**。ユーザー 1 つ + スクリプト 1 つで動く - **導入コスト最低**。ユーザー 1 つ + スクリプト 1 つで動く
- 宛先が引数に入るので `insomnia.pod-name@host` の見た目にはならない - 宛先が引数に入るので `yoi.pod-name@host` の見た目にはならない
### B. 鍵ベースルーティングgitolite 方式) ### B. 鍵ベースルーティングgitolite 方式)
``` ```
ssh insomnia@host # 使った鍵でどの Pod 宛か判別 ssh yoi@host # 使った鍵でどの Pod 宛か判別
``` ```
- `~insomnia/.ssh/authorized_keys` に Pod ごとのエントリ: - `~yoi/.ssh/authorized_keys` に Pod ごとのエントリ:
``` ```
command="insomnia-route pod-a",no-port-forwarding,... ssh-ed25519 AAAA... pod-a@remote command="yoi-route pod-a",no-port-forwarding,... ssh-ed25519 AAAA... pod-a@remote
command="insomnia-route pod-b",no-port-forwarding,... ssh-ed25519 AAAA... pod-b@remote command="yoi-route pod-b",no-port-forwarding,... ssh-ed25519 AAAA... pod-b@remote
``` ```
- SSH 接続時に使われた鍵が `command=` で指定されたルーティング先を決定 - SSH 接続時に使われた鍵が `command=` で指定されたルーティング先を決定
- gitolite / Gitea / Gogs で実証済みのパターン - gitolite / Gitea / Gogs で実証済みのパターン
- 接続元は `ssh insomnia@host` だけ。**鍵が宛先を決める** - 接続元は `ssh yoi@host` だけ。**鍵が宛先を決める**
- クライアント側 SSH config で alias を作れば見た目を整えられる: - クライアント側 SSH config で alias を作れば見た目を整えられる:
``` ```
Host pod-a.host-b Host pod-a.host-b
HostName host-b HostName host-b
User insomnia User yoi
IdentityFile ~/.config/insomnia/keys/pod-a IdentityFile ~/.config/yoi/keys/pod-a
``` ```
- 鍵の登録が相互に必要Pod A が Pod B に送るなら、B のマシンの - 鍵の登録が相互に必要Pod A が Pod B に送るなら、B のマシンの
authorized_keys に A の公開鍵 + route 先を登録) authorized_keys に A の公開鍵 + route 先を登録)
@ -91,15 +91,15 @@ ssh insomnia@host # 使った鍵でどの Pod 宛か判別
### C. 動的ユーザー名 ### C. 動的ユーザー名
``` ```
ssh insomnia.pod-name@host ssh yoi.pod-name@host
``` ```
- `insomnia.pod-name` を OS レベルで有効なユーザー名として解決する: - `yoi.pod-name` を OS レベルで有効なユーザー名として解決する:
- NSS (Name Service Switch) モジュールを書くか `libnss-extrausers` を利用 - NSS (Name Service Switch) モジュールを書くか `libnss-extrausers` を利用
- PAM モジュールで認証をフック - PAM モジュールで認証をフック
- `sshd_config``Match User insomnia.*` → `ForceCommand` でルーティング - `sshd_config``Match User yoi.*` → `ForceCommand` でルーティング
- **最も直感的なアドレッシング**だが OS レベルの設定が必要 - **最も直感的なアドレッシング**だが OS レベルの設定が必要
- insomnia をインストールするだけでは動かない(管理者権限での設定が要る) - yoi をインストールするだけでは動かない(管理者権限での設定が要る)
- コンテナ環境ではやりやすい(ユーザー管理を自由にできる) - コンテナ環境ではやりやすい(ユーザー管理を自由にできる)
### 推奨 ### 推奨
@ -143,7 +143,7 @@ Pod 協働では:
- broadcast = known-peers を iterate して個別送信 - broadcast = known-peers を iterate して個別送信
- 規模が数十 Pod なら十分実用的 - 規模が数十 Pod なら十分実用的
- 将来的に gossip protocol で peer 発見を自動化できるが、 - 将来的に gossip protocol で peer 発見を自動化できるが、
MVP では手動登録(`insomnia peer add pod-a@host-b`)で十分 MVP では手動登録(`yoi peer add pod-a@host-b`)で十分
## 受信側のルーティング ## 受信側のルーティング
@ -151,16 +151,16 @@ SSH 接続を受けた側が、宛先 Pod のローカル socket にルーティ
仕組みが必要。 仕組みが必要。
``` ```
[SSH 接続] → insomnia-route <pod-name> [SSH 接続] → yoi-route <pod-name>
workspace registry を参照 workspace registry を参照
/run/insomnia/.../pod-name.sock に転送 /run/yoi/.../pod-name.sock に転送
Pod が受信・処理 Pod が受信・処理
``` ```
`insomnia-route` は: `yoi-route` は:
1. workspace のレジストリを読んで pod-name の socket path を引く 1. workspace のレジストリを読んで pod-name の socket path を引く
2. socket に接続してメッセージを中継 2. socket に接続してメッセージを中継
3. 応答を SSH 接続に返す 3. 応答を SSH 接続に返す
@ -172,17 +172,17 @@ unique であれば workspace を指定しなくて済む。
## Daemon-less リモート Pod 生成SSH-only モデル) ## Daemon-less リモート Pod 生成SSH-only モデル)
リモートホスト上の Pod 生成は **daemon 無しで SSH だけで成立する** リモートホスト上の Pod 生成は **daemon 無しで SSH だけで成立する**
remote 側に必要なのは `insomnia` バイナリと SSH アクセスのみ。 remote 側に必要なのは `yoi` バイナリと SSH アクセスのみ。
### 前提 ### 前提
- insomnia は環境再現git clone, コンテナ構築等)を自身の責務としない。 - yoi は環境再現git clone, コンテナ構築等)を自身の責務としない。
作業対象のファイルがリモートに既にあるか、ユーザーが任意の手段で 作業対象のファイルがリモートに既にあるか、ユーザーが任意の手段で
用意する前提git clone, rsync, 手動配置、CI の checkout 等) 用意する前提git clone, rsync, 手動配置、CI の checkout 等)
- insomnia が転送するのは**セッション(会話履歴)と manifest overlay** - yoi が転送するのは**セッション(会話履歴)と manifest overlay**
だけ。コードベースの同期は外部に委ねる だけ。コードベースの同期は外部に委ねる
- コンテナ内で動かすか bare metal で動かすかも insomnia は問わない。 - コンテナ内で動かすか bare metal で動かすかも yoi は問わない。
`insomnia` バイナリが動くホストの fs 上で活動する主体がある、 `yoi` バイナリが動くホストの fs 上で活動する主体がある、
それだけが前提 それだけが前提
### フロー ### フロー
@ -193,7 +193,7 @@ host_a (spawner) host_b (remote)
├── ssh: session データを転送 ────────→ ファイル書き込み ├── ssh: session データを転送 ────────→ ファイル書き込み
├── ssh: profile / one-file manifest 入力を転送 ─→ 必要ならファイル書き込み ├── ssh: profile / one-file manifest 入力を転送 ─→ 必要ならファイル書き込み
├── ssh: `insomnia pod --profile ... &` ───────→ Pod プロセス起動、socket 作成 ├── ssh: `yoi pod --profile ... &` ───────→ Pod プロセス起動、socket 作成
├── ssh -L: socket を tunnel ─────────→ Pod B の unix socket ├── ssh -L: socket を tunnel ─────────→ Pod B の unix socket
└── localhost:tunnel に接続 ──────────→ Method::Run / Event stream └── localhost:tunnel に接続 ──────────→ Method::Run / Event stream
@ -204,16 +204,16 @@ host_a (spawner) host_b (remote)
```bash ```bash
# 1. session + profile/manifest input を転送 # 1. session + profile/manifest input を転送
ssh insomnia@host-b "mkdir -p ~/workspaces/task-123/store" ssh yoi@host-b "mkdir -p ~/workspaces/task-123/store"
tar cz session/ | ssh insomnia@host-b "tar xz -C ~/workspaces/task-123/store" tar cz session/ | ssh yoi@host-b "tar xz -C ~/workspaces/task-123/store"
scp profile.lua insomnia@host-b:~/workspaces/task-123/profile.lua scp profile.lua yoi@host-b:~/workspaces/task-123/profile.lua
# 2. Pod を起動detach # 2. Pod を起動detach
ssh insomnia@host-b "insomnia pod --store ~/workspaces/task-123/store \ ssh yoi@host-b "yoi pod --store ~/workspaces/task-123/store \
--profile ~/workspaces/task-123/profile.lua &" --profile ~/workspaces/task-123/profile.lua &"
# 3. socket を tunnel で引っ張る # 3. socket を tunnel で引っ張る
ssh -L /tmp/pod-b.sock:/run/insomnia/task-123/pod.sock insomnia@host-b ssh -L /tmp/pod-b.sock:/run/yoi/task-123/pod.sock yoi@host-b
# 4. あとは /tmp/pod-b.sock にローカルと同じ protocol で繋ぐ # 4. あとは /tmp/pod-b.sock にローカルと同じ protocol で繋ぐ
``` ```
@ -229,7 +229,7 @@ spawner の `SpawnPod` ツールがこの一連を内部で実行する。LLM
成立しないので、workspace の scope 会計は remote には関係しない 成立しないので、workspace の scope 会計は remote には関係しない
- **通知**: SSH tunnel が繋がっている限り `Event` stream がそのまま - **通知**: SSH tunnel が繋がっている限り `Event` stream がそのまま
流れる。tunnel が切れたら再接続する 流れる。tunnel が切れたら再接続する
- **環境構築は insomnia の責務外**: git clone するか rsync するかは - **環境構築は yoi の責務外**: git clone するか rsync するかは
Pod の instruction で指示するか、事前に用意されている前提 Pod の instruction で指示するか、事前に用意されている前提
### daemon が必要になるケース ### daemon が必要になるケース
@ -251,7 +251,7 @@ SSH-only モデルの制約が、daemon 導入の動機になる:
### リモート側のディレクトリ構成 ### リモート側のディレクトリ構成
``` ```
/home/insomnia/ ← insomnia システムユーザーの home /home/yoi/ ← yoi システムユーザーの home
├── workspaces/ ├── workspaces/
│ ├── <task-or-project-id>/ ← workspace ごとのルート │ ├── <task-or-project-id>/ ← workspace ごとのルート
│ │ ├── repo/ ← ユーザーが用意した作業ファイル群 │ │ ├── repo/ ← ユーザーが用意した作業ファイル群
@ -261,8 +261,8 @@ SSH-only モデルの制約が、daemon 導入の動機になる:
└── authorized_keys ← 接続元 Pod の公開鍵 └── authorized_keys ← 接続元 Pod の公開鍵
``` ```
- `insomnia` システムユーザーが SSH 接続先 + ファイル所有者 - `yoi` システムユーザーが SSH 接続先 + ファイル所有者
- `repo/` 配下の準備は insomnia の責務外git clone, rsync 等は - `repo/` 配下の準備は yoi の責務外git clone, rsync 等は
ユーザーや instruction が指示) ユーザーや instruction が指示)
- `store/` は spawner がセッションデータを書き込む場所 - `store/` は spawner がセッションデータを書き込む場所

View File

@ -2,7 +2,7 @@
## Context ## Context
INSOMNIA はエージェントが扱うツール数の増加 (built-in tools + MCP サーバ + ユーザ定義) を想定する必要がある。すべてを upfront に context へ展開すると以下が問題になる: Yoi はエージェントが扱うツール数の増加 (built-in tools + MCP サーバ + ユーザ定義) を想定する必要がある。すべてを upfront に context へ展開すると以下が問題になる:
- **入力トークン消費**: 30-50 ツールで 10-20K tokens を消費しうる (Anthropic 公式ガイド) - **入力トークン消費**: 30-50 ツールで 10-20K tokens を消費しうる (Anthropic 公式ガイド)
- **ツール選択精度の低下**: 数十個を超えるとモデルの tool selection accuracy が落ちる - **ツール選択精度の低下**: 数十個を超えるとモデルの tool selection accuracy が落ちる

View File

@ -24,8 +24,8 @@ Workflow は制約付きの強制的な作業フロー。`/<slug>` で明示的
### 格納先とファイル形式 ### 格納先とファイル形式
- `.insomnia/workflow/<slug>.md`(ファイル名 = slug がそのまま識別子、`name` field は持たない) - `.yoi/workflow/<slug>.md`(ファイル名 = slug がそのまま識別子、`name` field は持たない)
- `.insomnia/memory/` は session-derived state 専用、Workflow は配置しない - `.yoi/memory/` は session-derived state 専用、Workflow は配置しない
- frontmatter + Markdown 本文 - frontmatter + Markdown 本文
- frontmatter フィールド: `description`, `auto_invoke`, `user_invocable`, `requires` - frontmatter フィールド: `description`, `auto_invoke`, `user_invocable`, `requires`

View File

@ -22,10 +22,10 @@
Profile は Lua で書かれる。Rust resolver は selected profile を restricted Lua VM 内で評価し、返り値が Profile-shaped であることを検証してから `PodManifest` に変換する。 Profile は Lua で書かれる。Rust resolver は selected profile を restricted Lua VM 内で評価し、返り値が Profile-shaped であることを検証してから `PodManifest` に変換する。
```lua ```lua
local profile = require("insomnia.profile") local profile = require("yoi.profile")
local models = require("insomnia.models") local models = require("yoi.models")
local scope = require("insomnia.scope") local scope = require("yoi.scope")
local compact = require("insomnia.compact") local compact = require("yoi.compact")
local model = models.catalog("codex-oauth/gpt-5.5") local model = models.catalog("codex-oauth/gpt-5.5")
@ -79,9 +79,9 @@ Profile に入れてはいけないもの:
`.nix` profile files are no longer supported. Reusable profiles are Lua; complete low-level recipes belong behind `--manifest`. `.nix` profile files are no longer supported. Reusable profiles are Lua; complete low-level recipes belong behind `--manifest`.
Discovery は bundled builtin profiles、user registry (`<config_dir>/profiles.toml`)、project registry (`<project>/.insomnia/profiles.toml`) を読む。後段の default が前段の default を上書きするため、project default は user/default builtin より優先される。unqualified ambiguous names は source-qualified suggestion を出して失敗する。 Discovery は bundled builtin profiles、user registry (`<config_dir>/profiles.toml`)、project registry (`<project>/.yoi/profiles.toml`) を読む。後段の default が前段の default を上書きするため、project default は user/default builtin より優先される。unqualified ambiguous names は source-qualified suggestion を出して失敗する。
Example `.insomnia/profiles.toml`: Example `.yoi/profiles.toml`:
```toml ```toml
default = "coder" default = "coder"
@ -103,11 +103,11 @@ Profile evaluation runs with controlled host-provided `require`.
Host virtual modules: Host virtual modules:
- `require("insomnia")` - `require("yoi")`
- `require("insomnia.profile")` - `require("yoi.profile")`
- `require("insomnia.models")` - `require("yoi.models")`
- `require("insomnia.compact")` - `require("yoi.compact")`
- `require("insomnia.scope")` - `require("yoi.scope")`
Profile-local modules can be reused with dotted names such as `require("shared")` or `require("shared.models")`; they resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, unrestricted `package`, `dofile`, `loadfile`, `load`, and `collectgarbage` are unavailable by default. Profile-local modules can be reused with dotted names such as `require("shared")` or `require("shared.models")`; they resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, unrestricted `package`, `dofile`, `loadfile`, `load`, and `collectgarbage` are unavailable by default.
@ -146,22 +146,22 @@ One-file Manifest deserialization keeps the Manifest compatibility behavior: unk
| Prefix | Resolution | | Prefix | Resolution |
|---|---| |---|---|
| `$insomnia` | bundled `resources/prompts/` (`include_dir!`) | | `$yoi` | bundled `resources/prompts/` (`include_dir!`) |
| `$user` | `<config_dir>/prompts/` | | `$user` | `<config_dir>/prompts/` |
| `$workspace` | `<project>/.insomnia/prompts/` | | `$workspace` | `<project>/.yoi/prompts/` |
`.md` extension can be omitted, e.g. `$insomnia/default` resolves to `resources/prompts/default.md`. Missing files are hard errors; prefixes do not fall through. `.md` extension can be omitted, e.g. `$yoi/default` resolves to `resources/prompts/default.md`. Missing files are hard errors; prefixes do not fall through.
Profile and one-file Manifest CLI paths currently use builtin prompt assets only for initial loader construction. `$insomnia/...` works; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery. Profile and one-file Manifest CLI paths currently use builtin prompt assets only for initial loader construction. `$yoi/...` works; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery.
The rendered instruction body is followed by fixed Rust-provided sections for working boundaries and, when present, `AGENTS.md`. User templates cannot remove the scope section. The rendered instruction body is followed by fixed Rust-provided sections for working boundaries and, when present, `AGENTS.md`. User templates cannot remove the scope section.
## `insomnia pod` CLI ## `yoi pod` CLI
Normal fresh startup uses profile discovery/default selection: Normal fresh startup uses profile discovery/default selection:
```text ```text
insomnia pod [--profile <selector>] [--profile-pod-name <name>] [-s/--store <path>] yoi pod [--profile <selector>] [--profile-pod-name <name>] [-s/--store <path>]
``` ```
| Flag | Description | | Flag | Description |
@ -173,8 +173,8 @@ insomnia pod [--profile <selector>] [--profile-pod-name <name>] [-s/--store <pat
Restore/attach uses Pod/session state and does not re-evaluate profile sources. Restore/attach uses Pod/session state and does not re-evaluate profile sources.
```text ```text
insomnia pod --pod <name> yoi pod --pod <name>
insomnia pod --session <uuid> yoi pod --session <uuid>
``` ```
Spawn children use hidden `--spawn-config-json`, `--adopt`, and `--callback <path>` flags. These are internal handoff details used by `SpawnPod` after the parent has allocated scope and prepared the child config. Spawn children use hidden `--spawn-config-json`, `--adopt`, and `--callback <path>` flags. These are internal handoff details used by `SpawnPod` after the parent has allocated scope and prepared the child config.

View File

@ -1,6 +1,6 @@
# Reasoning / Thinking 制御 # Reasoning / Thinking 制御
manifest の `[worker]` セクションで `reasoning` を指定すると、scheme が provider 各社の wire 形式に投影する。文字列なら **effort label**、数値なら **thinking budget tokens** として扱う。insomnia 側は値の妥当性を検証せず、未知ラベルや provider が拒む値は API 応答で初めて検出される。 manifest の `[worker]` セクションで `reasoning` を指定すると、scheme が provider 各社の wire 形式に投影する。文字列なら **effort label**、数値なら **thinking budget tokens** として扱う。yoi 側は値の妥当性を検証せず、未知ラベルや provider が拒む値は API 応答で初めて検出される。
## 書き方 ## 書き方

View File

@ -37,7 +37,7 @@ Tier 3: Full Compact/compact コマンド)
**「十分なトークンを削れる場合にだけ実行」** という判断を行い、 **「十分なトークンを削れる場合にだけ実行」** という判断を行い、
キャッシュ無効化コスト > 節約トークン数 となるケースを避ける。 キャッシュ無効化コスト > 節約トークン数 となるケースを避ける。
これは Insomnia における条件付き Prune の直接的な先行事例。 これは Yoi における条件付き Prune の直接的な先行事例。
### キャッシュへの影響 ### キャッシュへの影響
@ -216,7 +216,7 @@ OpenCodesst/opencodeでも Prune とキャッシュの問題は未解決
--- ---
## Insomnia 設計への示唆 ## Yoi 設計への示唆
### 1. Prune は条件付きで実行すべき ### 1. Prune は条件付きで実行すべき

View File

@ -38,12 +38,12 @@
- `packages/opencode/src/session/prompt/anthropic-20250930.txt`Claude Code 風システムプロンプト) - `packages/opencode/src/session/prompt/anthropic-20250930.txt`Claude Code 風システムプロンプト)
- `opencode-anthropic-auth@0.0.13` ビルトインプラグイン - `opencode-anthropic-auth@0.0.13` ビルトインプラグイン
- `claude-code-20250219` beta ヘッダ - `claude-code-20250219` beta ヘッダ
- 代替検討: `claude -p` (Claude Code の headless mode) を subprocess で呼ぶ方式。ACP ではなく素朴な CLI fork であり、insomnia では採用しない - 代替検討: `claude -p` (Claude Code の headless mode) を subprocess で呼ぶ方式。ACP ではなく素朴な CLI fork であり、yoi では採用しない
- https://code.claude.com/docs/en/legal-and-compliance - https://code.claude.com/docs/en/legal-and-compliance
- https://github.com/sst/opencode/pull/18186 - https://github.com/sst/opencode/pull/18186
### OpenAI (Codex CLI / Responses) ### OpenAI (Codex CLI / Responses)
- Codex CLI は Apache-2.0 で公開されている。insomnia の Codex OAuth 経路は、Codex CLI と同じ Responses 系 wire behavior に寄せる - Codex CLI は Apache-2.0 で公開されている。yoi の Codex OAuth 経路は、Codex CLI と同じ Responses 系 wire behavior に寄せる
- Codex CLI の認証ストアと conversation header / request compression / SSE behavior を参考にする - Codex CLI の認証ストアと conversation header / request compression / SSE behavior を参考にする
- OpenCode の `/connect` で ChatGPT ブラウザ認証が通る - OpenCode の `/connect` で ChatGPT ブラウザ認証が通る
- コミュニティ評価: 「Anthropic は walled garden、OpenAI はむしろ取り込みに来た」 - コミュニティ評価: 「Anthropic は walled garden、OpenAI はむしろ取り込みに来た」
@ -54,7 +54,7 @@
- `claude --print` / `claude -p` は Claude Code の非対話headlessモード。プロンプトを stdin/引数で受け stdout に返す - `claude --print` / `claude -p` は Claude Code の非対話headlessモード。プロンプトを stdin/引数で受け stdout に返す
- **ACP ではなく素朴な subprocess 呼び出し** - **ACP ではなく素朴な subprocess 呼び出し**
- OpenClaw と OpenCode コミュニティフォーク (`griffinmartin/opencode-claude-auth`) が採用 - OpenClaw と OpenCode コミュニティフォーク (`griffinmartin/opencode-claude-auth`) が採用
- insomnia では専用 API integration ではないため採用しない - yoi では専用 API integration ではないため採用しない
## Ollama の統合機構 ## Ollama の統合機構
@ -177,7 +177,7 @@
## Capability 軸 ## Capability 軸
モデル/プロバイダごとの機能差を表現する軸。**プロバイダ側高次ツール (web_search / code_interpreter / computer_use / Live Search) は insomnia では使用しない方針**のため capability 軸から除外。 モデル/プロバイダごとの機能差を表現する軸。**プロバイダ側高次ツール (web_search / code_interpreter / computer_use / Live Search) は yoi では使用しない方針**のため capability 軸から除外。
### 1. tool calling ### 1. tool calling
parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のような「reasoner + tool 非対応」ケースあり。 parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のような「reasoner + tool 非対応」ケースあり。
@ -221,7 +221,7 @@ parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のよう
→ Gemini / Ollama `/v1` は scheme アダプタで「BlockStart → InputJson(全体 1 回) → BlockStop」の**擬似ストリーム化**で共通化可能。 → Gemini / Ollama `/v1` は scheme アダプタで「BlockStart → InputJson(全体 1 回) → BlockStop」の**擬似ストリーム化**で共通化可能。
## insomnia での採用方針 ## yoi での採用方針
### 第一級サポート(専用アダプタ) ### 第一級サポート(専用アダプタ)
- **Ollama API** — ローカル + `:cloud` サフィックスで透過的にクラウド中継。エンドポイントは `localhost:11434` で統一 - **Ollama API** — ローカル + `:cloud` サフィックスで透過的にクラウド中継。エンドポイントは `localhost:11434` で統一
@ -239,4 +239,4 @@ parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のよう
### 実装原則 ### 実装原則
- 認証アダプタ(外部 CLI の認証ストアを読む類)は llm-worker 直下ではなく上位アダプタ層に配置。llm-worker は低レベル基盤に留める原則project memoryと整合 - 認証アダプタ(外部 CLI の認証ストアを読む類)は llm-worker 直下ではなく上位アダプタ層に配置。llm-worker は低レベル基盤に留める原則project memoryと整合
- モデル列挙は `auto_discover` と宣言型の両輪。Ollama は自動、ルーター系は宣言 - モデル列挙は `auto_discover` と宣言型の両輪。Ollama は自動、ルーター系は宣言
- `ollama launch insomnia` 対応を視野に入れ、env 注入 (`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等) で起動設定を受け入れる作り - `ollama launch yoi` 対応を視野に入れ、env 注入 (`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等) で起動設定を受け入れる作り

View File

@ -1,6 +1,6 @@
# エージェント向けメモリ機構の外部事例 # エージェント向けメモリ機構の外部事例
調査日: 2026-04-21。本ドキュメントはユーザー依頼の3ソースOpenAI Codex Chronicle / Shann³ の "AI Knowledge Layer" スレッド / Nous Research Hermes Agentを中心に、直近で公開されたメモリ機構をまとめ、insomnia に入れる際の比較材料とすることを目的とする。2026年前半は各所からメモリ実装が同時多発的に登場しているので、「どの事例がどのレイヤを担っているか」を見失わないよう、各節で**何を記憶するか/いつ書くか/どう引き出すか/何に保存するか**を揃えて整理する。 調査日: 2026-04-21。本ドキュメントはユーザー依頼の3ソースOpenAI Codex Chronicle / Shann³ の "AI Knowledge Layer" スレッド / Nous Research Hermes Agentを中心に、直近で公開されたメモリ機構をまとめ、yoi に入れる際の比較材料とすることを目的とする。2026年前半は各所からメモリ実装が同時多発的に登場しているので、「どの事例がどのレイヤを担っているか」を見失わないよう、各節で**何を記憶するか/いつ書くか/どう引き出すか/何に保存するか**を揃えて整理する。
数値・URLは一次ソースで再確認すること。挙動は研究プレビュー段階のものが多く、変わる前提で読む。 数値・URLは一次ソースで再確認すること。挙動は研究プレビュー段階のものが多く、変わる前提で読む。
@ -25,7 +25,7 @@ Codex CLI に 2026-03 頃から追加された "Memories" 機能と、2026-04-15
- 生成タイミングは「スレッドが十分アイドルになってから」age + idle window で判定) - 生成タイミングは「スレッドが十分アイドルになってから」age + idle window で判定)
- `memory_summary.md`**5,000 tokens cap** で system prompt に注入される(`MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT` - `memory_summary.md`**5,000 tokens cap** で system prompt に注入される(`MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT`
**非対称の肝**: 抽出 (extract) は structured output で分類を強制、統合 (consolidation) は sub-agent に自由書き込みさせる。分類ブレを extract で封じ、統合の柔軟性は consolidation の agentic 判断に委ねる、という役割分担。insomnia の extract / consolidation 設計の直接の元ネタ。 **非対称の肝**: 抽出 (extract) は structured output で分類を強制、統合 (consolidation) は sub-agent に自由書き込みさせる。分類ブレを extract で封じ、統合の柔軟性は consolidation の agentic 判断に委ねる、という役割分担。yoi の extract / consolidation 設計の直接の元ネタ。
### 保存場所 / 形式 ### 保存場所 / 形式
@ -66,7 +66,7 @@ Codex CLI に 2026-03 頃から追加された "Memories" 機能と、2026-04-15
## 2. Shann³ "AI Knowledge Layer" ## 2. Shann³ "AI Knowledge Layer"
https://x.com/shannholmberg/status/2044111115878326444 で提唱している、**エージェントより先に読ませる知識層**という枠組み。エンジニア向けというよりマーケター / コンテンツ運用者向けだが、構造はかなり insomnia に転用しやすい。 https://x.com/shannholmberg/status/2044111115878326444 で提唱している、**エージェントより先に読ませる知識層**という枠組み。エンジニア向けというよりマーケター / コンテンツ運用者向けだが、構造はかなり yoi に転用しやすい。
### 2 層構造 ### 2 層構造
@ -128,7 +128,7 @@ MindStudio の比較記事が要点をまとめている。
### 設計上の示唆 ### 設計上の示唆
- **可変 KBL と不変 BF の分離**が強い。insomnia なら前者が Chronicle 的な自動メモリ、後者が `AGENTS.md` / 人間のガイド。 - **可変 KBL と不変 BF の分離**が強い。yoi なら前者が Chronicle 的な自動メモリ、後者が `AGENTS.md` / 人間のガイド。
- **retrieval を埋め込みではなく決定論的 wikilink でやる**アプローチは、少量ドメインで意外と強い。 - **retrieval を埋め込みではなく決定論的 wikilink でやる**アプローチは、少量ドメインで意外と強い。
- エージェントに書かせる `log.md``index.md` の append-only 運用は、後で diff / git で検証しやすい。 - エージェントに書かせる `log.md``index.md` の append-only 運用は、後で diff / git で検証しやすい。
@ -163,7 +163,7 @@ Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + ク
> Review the conversation above and consider saving or updating a skill if appropriate. > Review the conversation above and consider saving or updating a skill if appropriate.
> Focus on: was a non-trivial approach used to complete a task that required trial and error... > Focus on: was a non-trivial approach used to complete a task that required trial and error...
> **If nothing is worth saving, just say 'Nothing to save.' and stop.** > **If nothing is worth saving, just say 'Nothing to save.' and stop.**
- 末尾の "Nothing to save." 指示が肝。頻繁発火でも中身ゼロの場合は NOP で抜ける設計。insomnia extract の「空配列許容」の直接ソース - 末尾の "Nothing to save." 指示が肝。頻繁発火でも中身ゼロの場合は NOP で抜ける設計。yoi extract の「空配列許容」の直接ソース
### 書き込み機構 ### 書き込み機構
@ -180,7 +180,7 @@ Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + ク
### 設計上の示唆 ### 設計上の示唆
- **"skill" procedural memory として episodic / semantic から分離する**のは insomnia にとっても綺麗。Claude Code の skill と接続できる余地がある。 - **"skill" procedural memory として episodic / semantic から分離する**のは yoi にとっても綺麗。Claude Code の skill と接続できる余地がある。
- **FTS5 + LLM 要約ハイブリッド**で、ベクタを入れずにそこそこ回せる事例として参考価値が高い(少量ドメインなら LLM Wiki 論と同じ示唆)。 - **FTS5 + LLM 要約ハイブリッド**で、ベクタを入れずにそこそこ回せる事例として参考価値が高い(少量ドメインなら LLM Wiki 論と同じ示唆)。
- **Honcho 的 user model** を semantic profile として固定注入する運用は、Codex の memory summary と形式的に同じ。 - **Honcho 的 user model** を semantic profile として固定注入する運用は、Codex の memory summary と形式的に同じ。
@ -228,7 +228,7 @@ Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + ク
### OpenClaw ### OpenClaw
Peter Steinberger が 2025-11 に出したメッセージングファースト agent。2026-02 に本人は OpenAI 入社、プロジェクトは foundation へ移譲。Hermes Agent の `hermes claw migrate` はここからの移行導線。設計は **Markdown ファイルのみに全状態を持つ**という極端な透明性志向で、insomnia の「ファイル + git」方針との親和性が最も高い。 Peter Steinberger が 2025-11 に出したメッセージングファースト agent。2026-02 に本人は OpenAI 入社、プロジェクトは foundation へ移譲。Hermes Agent の `hermes claw migrate` はここからの移行導線。設計は **Markdown ファイルのみに全状態を持つ**という極端な透明性志向で、yoi の「ファイル + git」方針との親和性が最も高い。
Agent Workspace`~/.openclaw/workspace/`)構成: Agent Workspace`~/.openclaw/workspace/`)構成:
@ -263,7 +263,7 @@ Agent Workspace`~/.openclaw/workspace/`)構成:
- **Lock**: `memory/.dreams/short-term-promotion.lock``wx` フラグで exclusive create、60s stale 検出 + 10s wait timeout、in-process の Map も併用 - **Lock**: `memory/.dreams/short-term-promotion.lock``wx` フラグで exclusive create、60s stale 検出 + 10s wait timeout、in-process の Map も併用
- **モデルが "覚えている" のはディスクに書かれた内容だけ**、という明示ポリシー。隠れた state 無し - **モデルが "覚えている" のはディスクに書かれた内容だけ**、という明示ポリシー。隠れた state 無し
**insomnia にとって重要**: consolidation を LLM 依存から切り離せる見本。narrative は subagent が生成するが、promotion の判断は純機械scoringinsomnia の plan では Scope 外consolidation は当面 agent 委任)だが、成熟したカテゴリから決定論的 promotion に差し替える upgrade path の参考になる。 **yoi にとって重要**: consolidation を LLM 依存から切り離せる見本。narrative は subagent が生成するが、promotion の判断は純機械scoringyoi の plan では Scope 外consolidation は当面 agent 委任)だが、成熟したカテゴリから決定論的 promotion に差し替える upgrade path の参考になる。
**GC 観点の追加詳細**`extensions/memory-core/src/short-term-promotion.ts:1518-1652` 実装より): **GC 観点の追加詳細**`extensions/memory-core/src/short-term-promotion.ts:1518-1652` 実装より):
@ -278,7 +278,7 @@ OpenClaw は「**削除は人間、script は append と退避まで**」とい
設計上の示唆: 設計上の示唆:
- Workspace = git リポジトリ 1 本で完結、配置もフラット。insomnia の pod workspace 概念にそのまま借用できる。 - Workspace = git リポジトリ 1 本で完結、配置もフラット。yoi の pod workspace 概念にそのまま借用できる。
- 秘密は workspace **外**の `~/.openclaw/`auth / credentials / session transcripts / managed skillsに退避する分離設計は、pod sandbox 境界を越えない運用の手本になる。 - 秘密は workspace **外**の `~/.openclaw/`auth / credentials / session transcripts / managed skillsに退避する分離設計は、pod sandbox 境界を越えない運用の手本になる。
- `memory/YYYY-MM-DD.md` の日次切り分け + 「当日+前日のみ load」は、時系列の自然減衰を Markdown で素直に表現できる良い pattern。 - `memory/YYYY-MM-DD.md` の日次切り分け + 「当日+前日のみ load」は、時系列の自然減衰を Markdown で素直に表現できる良い pattern。
@ -298,7 +298,7 @@ OpenClaw は「**削除は人間、script は append と退避まで**」とい
## 5. Agent Skills 標準procedural memory の実装単位) ## 5. Agent Skills 標準procedural memory の実装単位)
Hermes / OpenClaw / Claude Code / OpenAI Codex / Cursor / GitHub Copilot 等が揃って採用している **`agentskills.io` オープン標準**。Anthropic が 2025-12 に発表し、2026-03 時点で事実上の共通フォーマットになっている。memory 設計の「手続き的記憶 (procedural memory)」のほぼ全てがこの単位で流通するので、insomnia でも独自フォーマットを避けて素直にこれに乗るのが合理的。 Hermes / OpenClaw / Claude Code / OpenAI Codex / Cursor / GitHub Copilot 等が揃って採用している **`agentskills.io` オープン標準**。Anthropic が 2025-12 に発表し、2026-03 時点で事実上の共通フォーマットになっている。memory 設計の「手続き的記憶 (procedural memory)」のほぼ全てがこの単位で流通するので、yoi でも独自フォーマットを避けて素直にこれに乗るのが合理的。
### SKILL.md の最小仕様 ### SKILL.md の最小仕様
@ -365,7 +365,7 @@ enterprise (managed) > personal (~/.claude/skills/) > project (.claude/skills/)
Skill content のライフサイクルは重要で、**一度発動すると rendered 内容が会話に単一メッセージとして入り、以後再読み込みされない**。auto-compaction 時は各 skill の直近 invocation だけが冒頭 5,000 tokens 維持され、全 skill 合算 25,000 tokens の予算内で新しい順に残す。つまり「skill の本体は session 中ずっと context に居座る」前提で書く必要がある。 Skill content のライフサイクルは重要で、**一度発動すると rendered 内容が会話に単一メッセージとして入り、以後再読み込みされない**。auto-compaction 時は各 skill の直近 invocation だけが冒頭 5,000 tokens 維持され、全 skill 合算 25,000 tokens の予算内で新しい順に残す。つまり「skill の本体は session 中ずっと context に居座る」前提で書く必要がある。
### insomnia への示唆 ### yoi への示唆
- **procedural memory は SKILL.md で表現**する。`.claude/skills/` に人間手入れの skill を置き、エージェントが自動生成する手続きは別ディレクトリ(例: `memory/skills/`で分離、混ざらないようにする。BF/KBL 分離の原則に一致。 - **procedural memory は SKILL.md で表現**する。`.claude/skills/` に人間手入れの skill を置き、エージェントが自動生成する手続きは別ディレクトリ(例: `memory/skills/`で分離、混ざらないようにする。BF/KBL 分離の原則に一致。
- **`paths:` によるスコープ絞り**は、前回議論した「Pod の所属スコープ = ディレクトリ階層」と自然に噛む。`paths: ["crates/protocol/**"]` の skill は protocol スコープの pod でだけ発動、という運用が素直にできる。 - **`paths:` によるスコープ絞り**は、前回議論した「Pod の所属スコープ = ディレクトリ階層」と自然に噛む。`paths: ["crates/protocol/**"]` の skill は protocol スコープの pod でだけ発動、という運用が素直にできる。
@ -382,7 +382,7 @@ Skill content のライフサイクルは重要で、**一度発動すると ren
## 6. プロンプト・スキルの継続的チューニング (empirical prompt tuning pattern) ## 6. プロンプト・スキルの継続的チューニング (empirical prompt tuning pattern)
メモリや skill の**中身を腐らせない**側の話。公開されている prompt tuning pattern として、agent-facing な指示を新規 subagent に実行させ、実行者の自己申告と指示側メトリクスを突き合わせて反復改善する方法がある。insomnia のように skill を蓄積する設計では、**書いた直後に客観的に試す**仕組みが無いと品質が崩れていく。ここへの素直な当てはめ材料として記録。 メモリや skill の**中身を腐らせない**側の話。公開されている prompt tuning pattern として、agent-facing な指示を新規 subagent に実行させ、実行者の自己申告と指示側メトリクスを突き合わせて反復改善する方法がある。yoi のように skill を蓄積する設計では、**書いた直後に客観的に試す**仕組みが無いと品質が崩れていく。ここへの素直な当てはめ材料として記録。
### 基本思想 ### 基本思想
@ -425,12 +425,12 @@ Claude Code の Task tool 戻り値から:
> 毎回**新規** AI を dispatch すること。同じセッション再利用は前回の指摘を学習してしまい、指標が腐る。 > 毎回**新規** AI を dispatch すること。同じセッション再利用は前回の指摘を学習してしまい、指標が腐る。
### insomnia への示唆 ### yoi への示唆
- **skill や lessons を新規追加した直後に、同じ insomnia ハーネス内の別 pod で実行して評価**する自動フロー("skill doctor" 的な存在)を作れる。これは insomnia が pod factory を持っている点と相性がいい。 - **skill や lessons を新規追加した直後に、同じ yoi ハーネス内の別 pod で実行して評価**する自動フロー("skill doctor" 的な存在)を作れる。これは yoi が pod factory を持っている点と相性がいい。
- 失敗ログを書いた後、「同じ失敗が再現しないか」を新規 pod で試走する検証ステップが、構造的に**メモリ整備の一部**に組み込める。skill 化しない失敗ログでも有効。 - 失敗ログを書いた後、「同じ失敗が再現しないか」を新規 pod で試走する検証ステップが、構造的に**メモリ整備の一部**に組み込める。skill 化しない失敗ログでも有効。
- 評価指標を自前で定義しておくと、後で他人or 未来の自分)が skill を更新した時に腐敗検知できる。 - 評価指標を自前で定義しておくと、後で他人or 未来の自分)が skill を更新した時に腐敗検知できる。
- 実体は skill 自身として配布されている例がある。insomnia のメンテ用 skill セットのテンプレにも応用できる。 - 実体は skill 自身として配布されている例がある。yoi のメンテ用 skill セットのテンプレにも応用できる。
一次ソースは公開 sanitize branch では省略する。 一次ソースは公開 sanitize branch では省略する。
@ -450,19 +450,19 @@ Claude Code の Task tool 戻り値から:
| ユーザー編集性 | 不可視 / 読取のみ / 手編集前提 | | ユーザー編集性 | 不可視 / 読取のみ / 手編集前提 |
| スコープ | per-user / per-project / shared across agents | | スコープ | per-user / per-project / shared across agents |
insomnia で意思決定すべきポイントはこの対応表: yoi で意思決定すべきポイントはこの対応表:
- **Pod / Agent の「skill」概念を Hermes 風に明示すべきか**。現状の controller.rs には "sub-agent spawn" はあるが、skill を書き出して再利用する仕組みは無い。 - **Pod / Agent の「skill」概念を Hermes 風に明示すべきか**。現状の controller.rs には "sub-agent spawn" はあるが、skill を書き出して再利用する仕組みは無い。
- **Codex Chronicle 風の "consolidation モデルを別途設定" 構成**は、insomnia の llm provider policyOllama / Codex OAuth / Anthropicと相性が良い。軽量 extract と重い consolidation を別プロバイダに張れる。 - **Codex Chronicle 風の "consolidation モデルを別途設定" 構成**は、yoi の llm provider policyOllama / Codex OAuth / Anthropicと相性が良い。軽量 extract と重い consolidation を別プロバイダに張れる。
- **LLM Wiki パターンを採用する場合**、既に `docs/``tickets/` が Markdown + git で運用されているので、`memory/` ディレクトリを足して Git で可観測にしておくのが自然。RAG やベクトル化より先に、wikilink / index.md / log.md で足りるか見極めるべき。 - **LLM Wiki パターンを採用する場合**、既に `docs/``tickets/` が Markdown + git で運用されているので、`memory/` ディレクトリを足して Git で可観測にしておくのが自然。RAG やベクトル化より先に、wikilink / index.md / log.md で足りるか見極めるべき。
- **storage 層**: SQLite は既存 crate 構成にもフィット。Cloudflare Agent Memory / Codex の SQLite + 最終 Markdown の 2 段は移植しやすい。 - **storage 層**: SQLite は既存 crate 構成にもフィット。Cloudflare Agent Memory / Codex の SQLite + 最終 Markdown の 2 段は移植しやすい。
- **prompt injection 対策**: Chronicle が注意書きしている通り、観測チャンネルを増やすと攻撃面が広がる。insomnia では pod の sandbox 境界とメモリ生成を同じ境界で括る必要がある。 - **prompt injection 対策**: Chronicle が注意書きしている通り、観測チャンネルを増やすと攻撃面が広がる。yoi では pod の sandbox 境界とメモリ生成を同じ境界で括る必要がある。
--- ---
## 8. GC 機構の横断比較 ## 8. GC 機構の横断比較
`docs/plan/memory.md` §GC は「consolidation とは別経路で memory を再評価し、drop / merge / split / `replaced` chain 整理を行う」ことを決めた段階で、判断主体と処理種別の仕様をこれから詰める。本節は他プロジェクトの GC 設計を共通の 6 軸で並べて、insomnia で採るべき型の材料とする。 `docs/plan/memory.md` §GC は「consolidation とは別経路で memory を再評価し、drop / merge / split / `replaced` chain 整理を行う」ことを決めた段階で、判断主体と処理種別の仕様をこれから詰める。本節は他プロジェクトの GC 設計を共通の 6 軸で並べて、yoi で採るべき型の材料とする。
### 8.1 比較表 ### 8.1 比較表
@ -499,7 +499,7 @@ insomnia で意思決定すべきポイントはこの対応表:
3. **cron / scheduled sweep**OpenClaw dreaming default `0 3 * * *`, Codex extension retention: 定期的・予測可能。人間 review との組み合わせがしやすい。 3. **cron / scheduled sweep**OpenClaw dreaming default `0 3 * * *`, Codex extension retention: 定期的・予測可能。人間 review との組み合わせがしやすい。
4. **ingest 時の即時**Cloudflare supersession: 書き込みの tx 内で完結、後続 GC 走査が要らない。topic key 設計が前提。 4. **ingest 時の即時**Cloudflare supersession: 書き込みの tx 内で完結、後続 GC 走査が要らない。topic key 設計が前提。
insomnia の plan は (2) consolidation で rewrite 許可を置きつつ、GC は (3) 方向で別経路という構造。これは Codex / OpenClaw の両方と整合する。 yoi の plan は (2) consolidation で rewrite 許可を置きつつ、GC は (3) 方向で別経路という構造。これは Codex / OpenClaw の両方と整合する。
**判断主体の 3 系統**: **判断主体の 3 系統**:
@ -507,13 +507,13 @@ insomnia の plan は (2) consolidation で rewrite 許可を置きつつ、GC
- **決定論 scoring → 閾値 gate → 機械適用**: OpenClaw Deep promotion。LLM の揺れを除き、コストも LLM コールゼロ。ただし対象が append 側のみで、削除には使われていない。 - **決定論 scoring → 閾値 gate → 機械適用**: OpenClaw Deep promotion。LLM の揺れを除き、コストも LLM コールゼロ。ただし対象が append 側のみで、削除には使われていない。
- **LLM agentic**: Codex consolidation / Hermes review / Letta sleep-time。判断の柔軟性block 内部分削除、context 依存の mergeを LLM に委ねる。 - **LLM agentic**: Codex consolidation / Hermes review / Letta sleep-time。判断の柔軟性block 内部分削除、context 依存の mergeを LLM に委ねる。
`docs/plan/memory.md` は consolidation が LLM agentic、GC も暫定的に **LLM agentic + Linter Warn 併用**としている。完全に一致する事例は **Codex consolidation の consolidation prompt**836 行で、「removed thread id を `MEMORY.md` から部分削除し、blockに他の thread が残っている場合は split / rewrite して保持」という手続きを自然言語で指示している。**insomnia は Linter 側に警告カテゴリ(類似 slug / `replaced` 滞留 / sources 過多 / staleを先に定義し、GC 実行の agent プロンプトはそれを入力にする**構造が素直。 `docs/plan/memory.md` は consolidation が LLM agentic、GC も暫定的に **LLM agentic + Linter Warn 併用**としている。完全に一致する事例は **Codex consolidation の consolidation prompt**836 行で、「removed thread id を `MEMORY.md` から部分削除し、blockに他の thread が残っている場合は split / rewrite して保持」という手続きを自然言語で指示している。**yoi は Linter 側に警告カテゴリ(類似 slug / `replaced` 滞留 / sources 過多 / staleを先に定義し、GC 実行の agent プロンプトはそれを入力にする**構造が素直。
**処理種別の選択肢**: **処理種別の選択肢**:
- `drop / merge / split / rewrite` の組み合わせは Codex consolidation が最も自由度高く、Hermes もそれに近いentry 粒度)。 - `drop / merge / split / rewrite` の組み合わせは Codex consolidation が最も自由度高く、Hermes もそれに近いentry 粒度)。
- `replaced` chain の整理は **Cloudflare だけが自動で版チェーン維持**、他は LLM 任せ。insomnia は decision record に `replaced_by` を入れているので、Cloudflare 方式の forward pointer 概念を **人間可読な `replaced_by:` frontmatter** で既に踏襲している。GC 時に chain をどこまで短く畳むか(長大な `a → b → c → d``a → d` に圧縮するかは未決定論点で、Cloudflare は圧縮せず chain を保持する設計。 - `replaced` chain の整理は **Cloudflare だけが自動で版チェーン維持**、他は LLM 任せ。yoi は decision record に `replaced_by` を入れているので、Cloudflare 方式の forward pointer 概念を **人間可読な `replaced_by:` frontmatter** で既に踏襲している。GC 時に chain をどこまで短く畳むか(長大な `a → b → c → d``a → d` に圧縮するかは未決定論点で、Cloudflare は圧縮せず chain を保持する設計。
- **`split` は Codex だけが明示**。block 内に複数 thread id が混ざった場合に thread id 単位で分ける。insomnia の「1 件 1 ファイル」方針では split = ファイル分割となり、主題の粒度判断は GC agent に委ねる必要がある。 - **`split` は Codex だけが明示**。block 内に複数 thread id が混ざった場合に thread id 単位で分ける。yoi の「1 件 1 ファイル」方針では split = ファイル分割となり、主題の粒度判断は GC agent に委ねる必要がある。
**人間介入点の 3 段**: **人間介入点の 3 段**:
@ -521,7 +521,7 @@ insomnia の plan は (2) consolidation で rewrite 許可を置きつつ、GC
- audit-firstissue を surface し、人間が決断): memory-wiki lint / OpenClaw dreaming-repair - audit-firstissue を surface し、人間が決断): memory-wiki lint / OpenClaw dreaming-repair
- high-stake 限定 gate: LinkedIn CMA - high-stake 限定 gate: LinkedIn CMA
insomnia の plan は「人間 offer 承認を併用」なので **audit-first に寄る**のが自然。lint 相当の Warn を Linter で出し、LLM consolidation / GC がそれを消費する前に人間が承認 / 拒否できる UI を提供する構造。memory-wiki lint は `reports/lint.md` というシンプルな Markdown 出力なので、そのまま `memory/reports/gc-lint.md` 相当を tick off する実装が参考になる。 yoi の plan は「人間 offer 承認を併用」なので **audit-first に寄る**のが自然。lint 相当の Warn を Linter で出し、LLM consolidation / GC がそれを消費する前に人間が承認 / 拒否できる UI を提供する構造。memory-wiki lint は `reports/lint.md` というシンプルな Markdown 出力なので、そのまま `memory/reports/gc-lint.md` 相当を tick off する実装が参考になる。
**履歴保持の 3 モデル**: **履歴保持の 3 モデル**:
@ -529,14 +529,14 @@ insomnia の plan は「人間 offer 承認を併用」なので **audit-first
2. **archive 退避rename**: OpenClaw dreaming-repair 2. **archive 退避rename**: OpenClaw dreaming-repair
3. **forward pointer / tombstone**: Cloudflare supersession 3. **forward pointer / tombstone**: Cloudflare supersession
insomnia**git 管理下に memory を置く**前提なので、物理削除を選んでも git log で復元できるのが強み。`replaced_by:` frontmatter が forward pointer の役割を果たしているので、Cloudflare 型と git を足した「**現物は物理削除、frontmatter pointer で chain を参照、git で history**」が最も設計コストに合う。archive 退避は git を前提にすると冗長。 yoi**git 管理下に memory を置く**前提なので、物理削除を選んでも git log で復元できるのが強み。`replaced_by:` frontmatter が forward pointer の役割を果たしているので、Cloudflare 型と git を足した「**現物は物理削除、frontmatter pointer で chain を参照、git で history**」が最も設計コストに合う。archive 退避は git を前提にすると冗長。
### 8.3 insomnia の GC 仕様を詰めるときの示唆 ### 8.3 yoi の GC 仕様を詰めるときの示唆
1. **GC trigger は 2 系統に割る**。(a) 決定論: Linter Warn 群 + age / count / size 閾値の sweep、(b) LLM 判定: consolidation とは別 prompt で Linter の issue リストを入力に渡す。両方が `memory/reports/gc-*.md` 相当を書き、それを次回の GC run が読む、というフィードバックループが OpenClaw lint / Codex consolidation input selection の両方と整合する。 1. **GC trigger は 2 系統に割る**。(a) 決定論: Linter Warn 群 + age / count / size 閾値の sweep、(b) LLM 判定: consolidation とは別 prompt で Linter の issue リストを入力に渡す。両方が `memory/reports/gc-*.md` 相当を書き、それを次回の GC run が読む、というフィードバックループが OpenClaw lint / Codex consolidation input selection の両方と整合する。
2. **Linter に「GC 候補検出」カテゴリを足す**。memory-wiki の lint issue code が参考になる: `stale-page`90d 超)/ `stale-claim` / `low-confidence` / `orphan` / `duplicate-id` / `broken-wikilink` / `contradiction-present` / `open-question`insomnia 固有の追加候補: `similar-slug`(類似 slug 乱立、既に plan に記載)/ `replaced-chain-long``replaced_by` が 3 段以上)/ `sources-overflow`1 record の sources が閾値超)/ `knowledge-invoke-frequency-low``user_invoke` が一定期間ゼロ)。 2. **Linter に「GC 候補検出」カテゴリを足す**。memory-wiki の lint issue code が参考になる: `stale-page`90d 超)/ `stale-claim` / `low-confidence` / `orphan` / `duplicate-id` / `broken-wikilink` / `contradiction-present` / `open-question`yoi 固有の追加候補: `similar-slug`(類似 slug 乱立、既に plan に記載)/ `replaced-chain-long``replaced_by` が 3 段以上)/ `sources-overflow`1 record の sources が閾値超)/ `knowledge-invoke-frequency-low``user_invoke` が一定期間ゼロ)。
3. **処理は rewrite 優先、削除は `status: replaced` 経由**(既に plan 方針と一致。forward pointer は Cloudflare 流、ただし chain 圧縮ルール(例: 「chain が n 段超えたら中間を drop、端のみ残す」を決めるかは別論点。Cloudflare は圧縮しない、insomnia は git があるので圧縮してよい。 3. **処理は rewrite 優先、削除は `status: replaced` 経由**(既に plan 方針と一致。forward pointer は Cloudflare 流、ただし chain 圧縮ルール(例: 「chain が n 段超えたら中間を drop、端のみ残す」を決めるかは別論点。Cloudflare は圧縮しない、yoi は git があるので圧縮してよい。
4. **char limit は採用しない方が筋が良い**。Hermes の hard limit + LLM self-rewrite は設計最小だが、insomnia は 1 record 1 file なのでファイル内 size 制約は薄く、file 数による grep コストの方が支配的になる。file 数閾値 → GC trigger の方が insomnia の形に合う。 4. **char limit は採用しない方が筋が良い**。Hermes の hard limit + LLM self-rewrite は設計最小だが、yoi は 1 record 1 file なのでファイル内 size 制約は薄く、file 数による grep コストの方が支配的になる。file 数閾値 → GC trigger の方が yoi の形に合う。
5. **決定論 scoring を後から差し込む余地を残す**。OpenClaw Deep pass のような「頻度 / 関連度 / 多様性 / 時間減衰 / 整合性 / 概念」の 6 重み + 閾値は、agent LLM の出力が運用で評価可能になった段階で部分的に差し替える upgrade path として最適。初期は consolidation LLM + Linter Warn で十分。 5. **決定論 scoring を後から差し込む余地を残す**。OpenClaw Deep pass のような「頻度 / 関連度 / 多様性 / 時間減衰 / 整合性 / 概念」の 6 重み + 閾値は、agent LLM の出力が運用で評価可能になった段階で部分的に差し替える upgrade path として最適。初期は consolidation LLM + Linter Warn で十分。
6. **削除は git commit 単位で可逆**という前提を明示する。プロジェクトメモリは git 管理下なので、GC が誤って drop してもユーザーは revert できる。これは Codex が持っていない利点で、GC agent の判断を多少攻めても安全マージンがある。 6. **削除は git commit 単位で可逆**という前提を明示する。プロジェクトメモリは git 管理下なので、GC が誤って drop してもユーザーは revert できる。これは Codex が持っていない利点で、GC agent の判断を多少攻めても安全マージンがある。

View File

@ -85,7 +85,7 @@ reasoning トークンは各ターンの後に破棄される。次ターンに
1. `previous_response_id` パラメータで過去のレスポンスを参照 1. `previous_response_id` パラメータで過去のレスポンスを参照
2. `response.output` の全アイテムを次の `input` に手動で渡す 2. `response.output` の全アイテムを次の `input` に手動で渡す
ステートレス利用(`store=false`、ZDR組織の場合は `include=["reasoning.encrypted_content"]` を指定すれば暗号化された推論コンテンツを受け取り、次リクエストに渡すことで推論を引き継げる。Insomnia は履歴から復元した reasoning item を通常の API message として扱い、独自の turn-boundary filtering はしない。 ステートレス利用(`store=false`、ZDR組織の場合は `include=["reasoning.encrypted_content"]` を指定すれば暗号化された推論コンテンツを受け取り、次リクエストに渡すことで推論を引き継げる。Yoi は履歴から復元した reasoning item を通常の API message として扱い、独自の turn-boundary filtering はしない。
同一ターン内の function-call loop でも、`reasoning item → function_call → function_call_output → 次の Responses request` の連続性を保つため、履歴上の reasoning item は通常の API message として保持する。ToolResult は wire 上で user 側 item に見えるが、reasoning item の削除境界としては扱わない。 同一ターン内の function-call loop でも、`reasoning item → function_call → function_call_output → 次の Responses request` の連続性を保つため、履歴上の reasoning item は通常の API message として保持する。ToolResult は wire 上で user 側 item に見えるが、reasoning item の削除境界としては扱わない。

View File

@ -1,18 +1,18 @@
# Insomnia × OpenCode 比較レポート # Yoi × OpenCode 比較レポート
## 概要 ## 概要
InsomniaRust製エージェントプラットフォーム、基礎実装段階と OpenCodeTypeScript/Bun製AIコーディングアシスタント、本番稼働レベルの設計を比較し、Insomniaの基礎設計に取り込めるパターンを特定する。 YoiRust製エージェントプラットフォーム、基礎実装段階と OpenCodeTypeScript/Bun製AIコーディングアシスタント、本番稼働レベルの設計を比較し、Yoiの基礎設計に取り込めるパターンを特定する。
--- ---
## 1. アーキテクチャ概観 ## 1. アーキテクチャ概観
### Insomnia(現状) ### Yoi(現状)
``` ```
insomnia (stub) yoi (stub)
└─ insomnia-core Pod / Controller / Protocol / SocketServer └─ yoi-core Pod / Controller / Protocol / SocketServer
└─ llm-worker-persistence Session永続化JSONL + Blob └─ llm-worker-persistence Session永続化JSONL + Blob
└─ llm-worker Worker / Tool / Hook / Subscriber └─ llm-worker Worker / Tool / Hook / Subscriber
└─ llm-worker-macros #[tool] / #[tool_registry] └─ llm-worker-macros #[tool] / #[tool_registry]
@ -41,13 +41,13 @@ packages/desktop (Tauri) Web UIラッパー
## 2. 設計判断の比較 ## 2. 設計判断の比較
| 観点 | Insomnia | OpenCode | 評価 | | 観点 | Yoi | OpenCode | 評価 |
|------|----------|----------|------| |------|----------|----------|------|
| **DI** | ジェネリクス `<C: LlmClient, St: Store>` | Effect Service + Layer | Insomnia: コンパイル時保証。OpenCode: 実行時合成の柔軟性。方向性は正しい | | **DI** | ジェネリクス `<C: LlmClient, St: Store>` | Effect Service + Layer | Yoi: コンパイル時保証。OpenCode: 実行時合成の柔軟性。方向性は正しい |
| **状態管理** | `RwLock<PodStatus>` + ファイル書き出し | SQLite + Event Bus + SSE | Insomnia: 軽量で正しい。DBは将来の選択肢 | | **状態管理** | `RwLock<PodStatus>` + ファイル書き出し | SQLite + Event Bus + SSE | Yoi: 軽量で正しい。DBは将来の選択肢 |
| **プロトコル** | 自前 JSONL (Method/Event) | Hono HTTP API + SSE | Insomnia: Unix Socketに最適化。目的が違う | | **プロトコル** | 自前 JSONL (Method/Event) | Hono HTTP API + SSE | Yoi: Unix Socketに最適化。目的が違う |
| **ツール** | `Tool` trait + マクロ生成 | Zod schema + execute関数 | 同等のアプローチ。マクロの方が型安全 | | **ツール** | `Tool` trait + マクロ生成 | Zod schema + execute関数 | 同等のアプローチ。マクロの方が型安全 |
| **フック** | `Hook<K: HookEventKind>` trait 10種 | Plugin hooks (before/after) | Insomnia: 型安全で粒度が細かい。OpenCode: 動的で拡張しやすい | | **フック** | `Hook<K: HookEventKind>` trait 10種 | Plugin hooks (before/after) | Yoi: 型安全で粒度が細かい。OpenCode: 動的で拡張しやすい |
| **永続化** | JSONL append-only + Blob | SQLite + Drizzle ORM | 方向性が異なる。両方とも正当な選択 | | **永続化** | JSONL append-only + Blob | SQLite + Drizzle ORM | 方向性が異なる。両方とも正当な選択 |
| **プロバイダ** | 4種Anthropic/OpenAI/Gemini/Ollama | 20種+ai-sdk経由 | 数は後から追加できる。抽象は同レベル | | **プロバイダ** | 4種Anthropic/OpenAI/Gemini/Ollama | 20種+ai-sdk経由 | 数は後から追加できる。抽象は同レベル |
@ -62,9 +62,9 @@ packages/desktop (Tauri) Web UIラッパー
- 3段階: `deny``allow``ask`(ユーザーに確認) - 3段階: `deny``allow``ask`(ユーザーに確認)
- 「always」応答でパターンを永続的に許可 - 「always」応答でパターンを永続的に許可
**Insomniaへの示唆:** **Yoiへの示唆:**
Insomniaには `Scope`(書き込みディレクトリ制約)があるが、これは静的な境界。 Yoiには `Scope`(書き込みディレクトリ制約)があるが、これは静的な境界。
ツール単位の動的パーミッションが欠落している。 ツール単位の動的パーミッションが欠落している。
``` ```
@ -101,12 +101,12 @@ action = "deny"
- 切り捨て分はファイルに保存7日間保持 - 切り捨て分はファイルに保存7日間保持
- LLMには「出力が大きすぎた。`grep` や `read` で絞り込め」とヒント - LLMには「出力が大きすぎた。`grep` や `read` で絞り込め」とヒント
**Insomniaの現状:** **Yoiの現状:**
- `llm-worker` に Tool Output の Inline/Stored 閾値800 bytesがある - `llm-worker` に Tool Output の Inline/Stored 閾値800 bytesがある
- Stored 出力は Blob Storage に退避し、要約を自動生成 - Stored 出力は Blob Storage に退避し、要約を自動生成
**比較:** **比較:**
Insomnia の方が洗練されている(要約生成まで組み込み済み)。 Yoi の方が洗練されている(要約生成まで組み込み済み)。
ただし OpenCode の「ヒント付きトランケーション」は追加の視点として有用。 ただし OpenCode の「ヒント付きトランケーション」は追加の視点として有用。
**取り込み案:** **取り込み案:**
@ -124,11 +124,11 @@ Insomnia の方が洗練されている(要約生成まで組み込み済み
- 構造化要約: Goal / Instructions / Discoveries / Accomplished / Files - 構造化要約: Goal / Instructions / Discoveries / Accomplished / Files
3. **Replay**: 圧縮後に前回のユーザーメッセージを再送して作業継続 3. **Replay**: 圧縮後に前回のユーザーメッセージを再送して作業継続
**Insomniaの現状:** **Yoiの現状:**
- Worker は history をそのまま保持 - Worker は history をそのまま保持
- コンテキスト管理の仕組みは未実装 - コンテキスト管理の仕組みは未実装
**これは重要な欠落。** 長時間実行エージェントである Insomnia にとって、コンテキスト管理はコア機能。 **これは重要な欠落。** 長時間実行エージェントである Yoi にとって、コンテキスト管理はコア機能。
**取り込み案:** **取り込み案:**
@ -157,13 +157,13 @@ consolidation: CompactAgent ベース)
- Instance スコープ + Global スコープの二段バス - Instance スコープ + Global スコープの二段バス
- `publish` / `subscribe` / `subscribeAll` の3操作 - `publish` / `subscribe` / `subscribeAll` の3操作
**Insomniaの現状:** **Yoiの現状:**
- `broadcast::Sender<Event>` による単一チャネル - `broadcast::Sender<Event>` による単一チャネル
- Event enum で型安全 - Event enum で型安全
- Pod 単位のスコープのみ - Pod 単位のスコープのみ
**比較:** **比較:**
Insomnia の broadcast channel は Pod 単位では十分。 Yoi の broadcast channel は Pod 単位では十分。
ただし、**複数 Pod の協調**Supervisor段階で Global Bus が必要になる。 ただし、**複数 Pod の協調**Supervisor段階で Global Bus が必要になる。
**取り込み案:** **取り込み案:**
@ -180,7 +180,7 @@ Insomnia の broadcast channel は Pod 単位では十分。
- ツール実行前にスナップショット取得 - ツール実行前にスナップショット取得
- `restore` / `revert` / `diff` 操作 - `restore` / `revert` / `diff` 操作
**Insomniaの現状:** **Yoiの現状:**
- Scope書き込み制約はあるが、変更追跡・復元は未実装 - Scope書き込み制約はあるが、変更追跡・復元は未実装
**取り込み案:** **取り込み案:**
@ -200,13 +200,13 @@ Insomnia の broadcast channel は Pod 単位では十分。
- `steps` パラメータでサブエージェントの反復回数を制限 - `steps` パラメータでサブエージェントの反復回数を制限
- 親セッションのコンテキストを子に渡す - 親セッションのコンテキストを子に渡す
**Insomniaの現状:** **Yoiの現状:**
- Pod は独立実行単位。Pod 間通信は未実装 - Pod は独立実行単位。Pod 間通信は未実装
- 拡張ポイント表に「Supervisor」として記載 - 拡張ポイント表に「Supervisor」として記載
**比較:** **比較:**
OpenCode の Agent は Session 内のモード切り替え。 OpenCode の Agent は Session 内のモード切り替え。
Insomnia の Pod は完全に独立したプロセス。 Yoi の Pod は完全に独立したプロセス。
**取り込み案:** **取り込み案:**
- OpenCode の `steps`(最大反復回数)は Pod マニフェストに追加する価値あり - OpenCode の `steps`(最大反復回数)は Pod マニフェストに追加する価値あり
@ -225,7 +225,7 @@ Insomnia の Pod は完全に独立したプロセス。
- 配列フィールドはマージ(上書きではなく結合) - 配列フィールドはマージ(上書きではなく結合)
- Plugin の出自を追跡PluginOrigin - Plugin の出自を追跡PluginOrigin
**Insomniaの現状:** **Yoiの現状:**
- マニフェストTOMLのみ。階層なし - マニフェストTOMLのみ。階層なし
**取り込み案:** **取り込み案:**
@ -244,7 +244,7 @@ Insomnia の Pod は完全に独立したプロセス。
- 遅延初期化(必要時にのみ起動) - 遅延初期化(必要時にのみ起動)
- graceful degradationサーバーなし → 無視) - graceful degradationサーバーなし → 無視)
**Insomniaの現状:** **Yoiの現状:**
- LSP の言及なし - LSP の言及なし
**取り込み案:** **取り込み案:**
@ -256,7 +256,7 @@ Insomnia の Pod は完全に独立したプロセス。
## 4. 設計思想の根本的な違い ## 4. 設計思想の根本的な違い
### Insomnia: 「Pod は独立した実行単位」 ### Yoi: 「Pod は独立した実行単位」
- 各 Pod が完結したプロセス - 各 Pod が完結したプロセス
- 協調は外部Supervisorが行う - 協調は外部Supervisorが行う
@ -269,7 +269,7 @@ Insomnia の Pod は完全に独立したプロセス。
- アプリケーションの哲学に近い - アプリケーションの哲学に近い
**この違いは意図的であり、変える必要はない。** **この違いは意図的であり、変える必要はない。**
Insomnia のアプローチは長時間自律実行に適しており、Pod の独立性がフォールトトレランスと拡張性の基盤になる。 Yoi のアプローチは長時間自律実行に適しており、Pod の独立性がフォールトトレランスと拡張性の基盤になる。
--- ---

View File

@ -16,7 +16,7 @@ Retrieved: 2026-04-28
**必須 (required)**。 **必須 (required)**。
`POST /v1/messages` のボディパラメータとして必須指定。 `POST /v1/messages` のボディパラメータとして必須指定。
insomnia の現在の実装(`max_tokens: u32`、未指定時 4096 にフォールバック)は仕様と合致している。 yoi の現在の実装(`max_tokens: u32`、未指定時 4096 にフォールバック)は仕様と合致している。
## 3. 型・範囲 ## 3. 型・範囲

View File

@ -211,7 +211,7 @@ Worker<C, Mutable> Worker<C, Locked>
- **需要がある層か**: yes。rig / swiftide / genai は揃って "もう一段下のキャッシュ整合性プリミティブ" を持っていない - **需要がある層か**: yes。rig / swiftide / genai は揃って "もう一段下のキャッシュ整合性プリミティブ" を持っていない
- **既存と被るか**: 上記 3 案でかわせる - **既存と被るか**: 上記 3 案でかわせる
- **維持コスト**: API 安定化 + provider 追従が恒常的に乗る。`llm_client/` を外すか genai に寄せるかでだいぶ軽くなる - **維持コスト**: API 安定化 + provider 追従が恒常的に乗る。`llm_client/` を外すか genai に寄せるかでだいぶ軽くなる
- **タイミング**: insomnia 本体がリリースされ、`Worker` の API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。**先に insomnia をリリースして、production 使用例として参照させてからライブラリ化する**方が安全 - **タイミング**: yoi 本体がリリースされ、`Worker` の API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。**先に yoi をリリースして、production 使用例として参照させてからライブラリ化する**方が安全
--- ---

View File

@ -48,7 +48,7 @@ let prompt_cache_key = Some(self.client.state.conversation_id.to_string());
シナリオ(マルチテナント等)で意図しないヒット混線を避ける用途 シナリオ(マルチテナント等)で意図しないヒット混線を避ける用途
で使う。少なくとも害は無いので両 backend で同じ値を送って良い。 で使う。少なくとも害は無いので両 backend で同じ値を送って良い。
## 5. insomnia での運用 ## 5. yoi での運用
- `Request::cache_key: Option<String>` を provider-agnostic な - `Request::cache_key: Option<String>` を provider-agnostic な
キャッシュヒントとして持つ。`cache_anchor` (Anthropic 用 prefix キャッシュヒントとして持つ。`cache_anchor` (Anthropic 用 prefix

View File

@ -84,7 +84,7 @@ LLMAI エージェント時代のコーディングで、Git の粒度が粗
- **永続的位置参照**: Tree-sitter ベースの semantic anchor、Sourcegraph の SCIP、`git-blame` の line tracking。DeltaDB は CRDT identity を使うため理論的にこれらより堅牢な anchor を提供できる。 - **永続的位置参照**: Tree-sitter ベースの semantic anchor、Sourcegraph の SCIP、`git-blame` の line tracking。DeltaDB は CRDT identity を使うため理論的にこれらより堅牢な anchor を提供できる。
- **AI エージェント協働基盤**: OpenAI の "evolving spec" 議論、Anthropic の Computer Use 系、各社の MCP。DeltaDB は「エージェント↔コード↔人間の対話」を VCS 層で受ける狙い。 - **AI エージェント協働基盤**: OpenAI の "evolving spec" 議論、Anthropic の Computer Use 系、各社の MCP。DeltaDB は「エージェント↔コード↔人間の対話」を VCS 層で受ける狙い。
## 8. 自プロジェクト(insomnia)への含意メモ ## 8. 自プロジェクト(yoi)への含意メモ
参考材料として残す(採用の可否ではない)。 参考材料として残す(採用の可否ではない)。

Some files were not shown because too many files have changed in this diff Show More