Default thinking level for new sessions, and move the rigs out of AGENTS.md
`Config::default_effort` is what a session starts at when nothing chose one, applied in `spawn_session` rather than filled in by the spawn screen so it holds for an import and a bare API call too. It is set by the spawn screen's own picker, whose label says so: one control, where new sessions are made, rather than a settings page for a single value. Not on a provider, because providers are discovered and the next rediscovery would erase it; not on the phone, because a second device would then spawn at a level nobody there chose. `GET`/`POST /defaults` carry it as a struct, so the permission mode -- still hardcoded to `auto` on the spawn screen -- can move there later without a second route. Only drivers that read a level are given one: an echo session was storing a `--effort` it never passes to anything, which is a config file answering a question about itself wrongly. Separately, `AGENTS.md` is 35 KB sent with every request in this repo, and 12 KB of it was rigs and reference measurements that only matter once you are running one. Those are the `ai-app-rigs` skill now -- the same text, still the only copy, read when the work touches it. 35,198 -> 20,813 chars. Verified on the emulator against the sandbox: the spawn screen pre-fills from the server, picking `low` spawned a session at `low` and left `/defaults` set to it, and an echo session spawned afterwards took no level at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1ff662c7c3
commit
4821a02bd3
8 files changed
+468
-235
No files matched your search
@@ -30,6 +30,18 @@ pub struct Config {
|
||||
pub tokens: Vec<TokenEntry>,
|
||||
pub setups: Vec<SetupConfig>,
|
||||
pub sessions: Vec<SessionConfig>,
|
||||
/// What a new session's thinking level is when nothing chose one.
|
||||
///
|
||||
/// Here rather than on a provider because providers are *discovered*: a
|
||||
/// default written onto one would be erased by the next rediscovery, which
|
||||
/// is the kind of setting that looks like it stuck until the day it did
|
||||
/// not. Here rather than on the phone because a second device would then
|
||||
/// spawn sessions the first one's owner did not expect.
|
||||
///
|
||||
/// `None` is the CLI's own default, and stays reachable: this is a level
|
||||
/// somebody chose, not a level this app picked for them.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub default_effort: Option<String>,
|
||||
}
|
||||
|
||||
/// A machine, and the things it can run.
|
||||
@@ -462,6 +474,7 @@ mod tests {
|
||||
}],
|
||||
},
|
||||
],
|
||||
default_effort: Some("low".to_string()),
|
||||
sessions: vec![SessionConfig {
|
||||
id: "abc123".to_string(),
|
||||
setup: "vm".to_string(),
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
//! POST /sessions/{id}/notify {notify} -- announce this one or not
|
||||
//! GET /notifications SSE: every session's attention-wanting
|
||||
//! moments, live only (see `notifications`)
|
||||
//! GET /defaults {effort} -- what a new session starts at
|
||||
//! POST /defaults {effort} -- null for the CLI's own default
|
||||
//! GET /usage cached usage windows per provider
|
||||
//! GET /models downloaded GGUFs, and what is being fetched
|
||||
//! GET /models/search?q=Q HuggingFace repositories matching Q
|
||||
@@ -137,6 +139,7 @@ pub fn router(manager: Arc<SessionManager>) -> Router {
|
||||
.route("/sessions/{id}/model", post(set_model))
|
||||
.route("/sessions/{id}/permission-mode", post(set_permission_mode))
|
||||
.route("/sessions/{id}/effort", post(set_effort))
|
||||
.route("/defaults", get(defaults).post(set_defaults))
|
||||
.route("/sessions/{id}/notify", post(set_notify))
|
||||
.route("/notifications", get(notifications))
|
||||
.route("/sessions/{id}/compact", post(compact))
|
||||
@@ -1355,6 +1358,36 @@ struct PermissionModeRequest {
|
||||
mode: String,
|
||||
}
|
||||
|
||||
/// What new sessions start at. One field today; a struct rather than a bare
|
||||
/// value because "the defaults" is the thing a phone asks for, and the next
|
||||
/// one to move here -- the permission mode, which the spawn screen still
|
||||
/// hardcodes -- must not need a second route.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Defaults {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
effort: Option<String>,
|
||||
}
|
||||
|
||||
async fn defaults(State(manager): State<Arc<SessionManager>>) -> axum::Json<Defaults> {
|
||||
axum::Json(Defaults {
|
||||
effort: manager.default_effort(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets what a new session's thinking level is. Applied when a session is
|
||||
/// spawned, so nothing already running changes underneath anybody.
|
||||
async fn set_defaults(
|
||||
State(manager): State<Arc<SessionManager>>,
|
||||
axum::Json(body): axum::Json<Defaults>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
manager
|
||||
.set_default_effort(body.effort.as_deref())
|
||||
.map_err(bad_request)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
||||
+102
-1
@@ -1038,7 +1038,19 @@ impl SessionManager {
|
||||
model: spec.model,
|
||||
cwd: spec.cwd,
|
||||
permission_mode: spec.permission_mode,
|
||||
effort: spec.effort,
|
||||
// Applied here rather than on the spawn screen, so it holds
|
||||
// however a session was made -- the phone, an import, or a bare
|
||||
// API call -- instead of only where somebody remembered to fill it
|
||||
// in. And only where the driver reads one: a llama session storing
|
||||
// a level it never passes to anything is a config file that
|
||||
// answers a question about itself wrongly.
|
||||
effort: spec.effort.or_else(|| {
|
||||
provider
|
||||
.kind
|
||||
.takes_effort()
|
||||
.then(|| inner.config.default_effort.clone())
|
||||
.flatten()
|
||||
}),
|
||||
params: spec.params,
|
||||
// On by default. Not offered at spawn: a session's first turn
|
||||
// is exactly the one somebody is waiting for.
|
||||
@@ -1283,6 +1295,27 @@ impl SessionManager {
|
||||
///
|
||||
/// `None` clears it, which is a level in its own right -- the CLI's own
|
||||
/// default -- and the reason this takes an option rather than a string.
|
||||
/// What a new session's thinking level is when nothing chose one, and the
|
||||
/// setting of it. See `Config::default_effort`; `None` is the CLI's own.
|
||||
///
|
||||
/// Only the default: a session already spawned keeps the level it was
|
||||
/// given, because changing what running conversations do from a screen
|
||||
/// about *new* ones is not something anybody asked for by setting a
|
||||
/// default.
|
||||
pub fn default_effort(&self) -> Option<String> {
|
||||
self.inner.read().unwrap().config.default_effort.clone()
|
||||
}
|
||||
|
||||
pub fn set_default_effort(&self, effort: Option<&str>) -> Result<()> {
|
||||
let effort = effort.map(str::trim).filter(|level| !level.is_empty());
|
||||
let mut inner = self.inner.write().unwrap();
|
||||
let mut candidate = inner.config.clone();
|
||||
candidate.default_effort = effort.map(str::to_string);
|
||||
candidate.save(&self.config_path)?;
|
||||
inner.config = candidate;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_session_effort(&self, id: &str, effort: Option<&str>) -> Result<()> {
|
||||
let effort = effort.map(str::trim).filter(|level| !level.is_empty());
|
||||
{
|
||||
@@ -3480,6 +3513,74 @@ mod tests {
|
||||
std::fs::write(path, rewritten).expect("write transcript");
|
||||
}
|
||||
|
||||
/// A new session takes the stored default, and an explicit choice still
|
||||
/// wins over it.
|
||||
///
|
||||
/// Applied where the session is made rather than on the spawn screen, so
|
||||
/// it holds for an import and a bare API call too -- a default that only
|
||||
/// worked from one screen would be a default somebody had already set and
|
||||
/// would reasonably believe was in force.
|
||||
#[tokio::test]
|
||||
async fn a_new_session_starts_at_the_stored_default_thinking_level() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let config_path = dir.path().join("config.ron");
|
||||
let data_dir = dir.path().join("sessions");
|
||||
// Seeded with both kinds, because half of what this asks is that a
|
||||
// driver which does not read a level is not given one.
|
||||
let cli = seed_stand_in_cli(&config_path, dir.path());
|
||||
let manager = SessionManager::new(
|
||||
config_path.clone(),
|
||||
data_dir.clone(),
|
||||
data_dir.join("models"),
|
||||
)
|
||||
.expect("manager");
|
||||
assert_eq!(
|
||||
manager.default_effort(),
|
||||
None,
|
||||
"nothing is set to begin with"
|
||||
);
|
||||
|
||||
manager
|
||||
.set_default_effort(Some("low"))
|
||||
.expect("store the default");
|
||||
let took = manager.spawn_session(stand_in_spec(&cli)).expect("spawn");
|
||||
assert_eq!(
|
||||
took.effort.as_deref(),
|
||||
Some("low"),
|
||||
"a new session takes it"
|
||||
);
|
||||
|
||||
let chosen = manager
|
||||
.spawn_session(SpawnSpec {
|
||||
effort: Some("max".to_string()),
|
||||
..stand_in_spec(&cli)
|
||||
})
|
||||
.expect("spawn");
|
||||
assert_eq!(
|
||||
chosen.effort.as_deref(),
|
||||
Some("max"),
|
||||
"an explicit choice is not overwritten by the default"
|
||||
);
|
||||
|
||||
// The case this change had no reason to touch: echo does not read a
|
||||
// level, so storing one on it would be a config file describing a
|
||||
// session in terms of something that never reaches it.
|
||||
let echo = manager.spawn_session(echo_spec()).expect("spawn echo");
|
||||
assert_eq!(
|
||||
echo.effort, None,
|
||||
"a driver that does not take a level is not given the default"
|
||||
);
|
||||
|
||||
// Clearing it is reachable, so the CLI's own default can be restored.
|
||||
manager.set_default_effort(None).expect("clear the default");
|
||||
let cleared = manager.spawn_session(stand_in_spec(&cli)).expect("spawn");
|
||||
assert_eq!(cleared.effort, None, "and then new sessions choose nothing");
|
||||
|
||||
for id in [took.id, chosen.id, echo.id, cleared.id] {
|
||||
manager.delete_session(&id).expect("delete");
|
||||
}
|
||||
}
|
||||
|
||||
/// A thinking level is stored and the process **ended**, because `--effort`
|
||||
/// is read when the CLI launches and has no control request behind it. A
|
||||
/// session left running would go on thinking at the old level underneath a
|
||||
|
||||
Reference in new issue
Block a user