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:
irisandClaude Opus 5 committed 2026-09-04 21:42:05 -04:00
1 parent 1ff662c7c3
commit 4821a02bd3
8 files changed
+468 -235

No files matched your search

+13
View File
@@ -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(),
+33
View File
@@ -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
View File
@@ -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