Skip to content

Basic (Q&A) and Agentic modes, a 270-model tested list, and the agent in installers - #2207

Merged
MODSetter merged 45 commits into
devfrom
dev_mod
Oct 8, 2026
Merged

MODSetter merged 45 commits into
devfrom
dev_mod

Conversation

@MODSetter

@MODSetter MODSetter commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

This release makes Agentic mode available to every chat model, widens the tested model list from 11 to 270, and ships the agent in installers.

What changes

Basic (Q&A) and Agentic, chosen per chat

  • The switch: the composer has a Basic (Q&A) / Agentic switch for a new chat. A chat keeps the mode it opened in. In an open chat the switch shows its mode, and choosing the other one offers a new chat in that mode.
  • No score blocks Agentic. Only technical gates do: a model stated not to call tools, a window under 32,768 tokens, or no opencode installed.
  • The default mode:
    • Agentic for a model measured at agent level on a remote host.
    • Basic for everything else (low scorers, untested models, local copies), with Agentic offered and a note.
  • Remembered: the mode the user picks is remembered per model, in chat_modes, and survives switching models.
  • Removed: Settings' "Try the agent" and its route are gone. A model with the old opt-in reads as Agentic.
  • Thread creation: it takes mode and remember.
    • A gated Agentic request gets a 409 with a code; one where opencode doesn't start gets a 503.
    • The question goes back to the composer, and a gate the API reports keeps Agentic off for that model.
  • Images: an Agentic chat takes no image attachments, which the agent refuses today.
  • Matching: model ids match across servers through a looser key that ignores quantisation tags, .gguf, -instruct/-it/-chat and Ollama name:tag. It never matches across sizes or versions.

A wider tested list: 270 rows

  • A screening sweep (tests/live/sweep, 7–8 Oct 2026) ran every OpenRouter model that calls tools, holds 32,768 tokens and costs under $3/M of input.
    • Cases: a smoke check, then the PDF brief and the board pack, one run each, with one re-run when the two cases split.
    • Result: 106 passed both (agent) and 102 did not (studio_only).
  • 51 assumed rows, never run:
    • models from $3/M of input;
    • the current reasoning flagships whose output costs $10/M or more;
    • models whose case went past the sweep's $3 stop.
  • Untested: 11 models the providers kept rate-limiting have no row.
  • Cost: $65.41.
  • Generation: scripts/catalog_rows.py turns the sweep's results into rows. The board pack's "says what it assumed" check is now a grade, not a pass/fail.

A fix found by the sweep: OpenAI's models fill every field a tool lists, so a render arrived with artifact_id: 0 and template_source_id: 0. The tool refused source 0 and the model repeated the call, so no OpenAI model could make a document in Agentic. An optional *_id of 0 now counts as left out. Re-run on the fix, o3, GPT-4.1, GPT-5 mini, GPT-5.4 mini, GPT-5.1 Codex mini, GPT-5.6 Luna Pro, GPT-6 Luna and both Grok models pass.

Installers ship the agent

  • ENABLED_BY_DEFAULT = true, so every build stages opencode 1.18.34 and ripgrep. SURFSENSE_LOCAL_OPENCODE_ENABLED accepts 1 or 0 and refuses anything else.
  • Checking the bundled copy: an electron-builder afterSign hook runs it before any installer is made, and the release workflow smoke-tests it on all three runners.
  • macOS signing: opencode keeps its vendor Developer ID signature (mac.signIgnore). It needs an entitlement the app's shared file lacks.
  • Size: installers grow by about 45–60 MB.

Smaller fixes

  • Streaming tests now wait past the 32 ms text notice gap; they were flaky under load.
  • The lane-port tests take ports far from any running sweep.

Tests

  • Frontend: typecheck, lint and translations (1,179 keys in 10 languages) are clean. The full Vitest run is 762/765; the 3 failures are timing tests that also fail on dev_mod and pass when run alone.
  • Backend:
    • 3,964 unit tests pass. test_probe was deselected; it fails on dev_mod too.
    • tests/integration/agent and tests/integration/chat pass (517, plus 13 re-run after a full disk).
    • Ruff and check_docs.py are clean.
  • Electron: typecheck passes, and the full suite has 170 passes. The 2 failures, secret.test.ts and opencode-leftovers.test.ts, already fail on Windows.
  • Known: 20 tests/integration/llm install-route tests fail on this machine on dev_mod too. They can't find ggml.dll in the worktree's backend folder, an environment issue.

Before the release

  • Apple: the expired developer agreement must be accepted, or macOS notarization keeps failing with a 403.
  • Dry run: run Desktop Release once with publish set to never, and check the opencode check is green on all three OSes, including the notarized Mac copy, which is signed by the vendor's team.
  • Real installs: install the signed Setup.exe on a clean Windows machine with Defender on, and the notarized DMG, then start an Agentic chat on each.

Summary by CodeRabbit

  • New Features
    • New chats can be started in Basic (Q&A) or Agentic mode, with the choice remembered for future chats. Existing chats keep their selected mode.
    • Agentic mode is available across models unless a technical requirement blocks it; model results help determine the default mode.
    • Installers now include the agent by default.
  • Updates
    • The chat composer explains when Agentic mode is unavailable and does not support image attachments; drafts are preserved if chat creation fails.
    • Model capability details now show Agentic availability, default mode, and test results.

Every model may now choose the agent, so every build stages opencode and
ripgrep: ENABLED_BY_DEFAULT is true, and SURFSENSE_LOCAL_OPENCODE_ENABLED=0
leaves them out of one build. The docs that said installers carry no agent now
say they do, and packaging.md records the Windows stage's size (185 MB
unpacked, 44 MB as xz) and that electron-builder signs opencode and rg with the
app's inherited entitlements, whose allow-jit Bun needs.
Now that every installer carries the agent, a release that ships without it,
or with an opencode that signing broke, would pass CI. Each runner now runs
check-stage.mjs on the opencode folder its build packaged: both executables and
their licences are there, and opencode and rg report their pinned versions.
On macOS that runs the copy signed under the hardened runtime, and on a tag's
Windows build the copy Azure signed. stage.mjs runs the same check before its
swap.
free_port() binds port 0 and lets go, and seconds later opencode or
uvicorn binds the number; a parallel run, or any outgoing connection,
can take it in between. On the ladder that left opencode unserved and
the thread opened as a chat. With SURFSENSE_LIVE_PORT_BASE set, the
app's and opencode's ports come from a 100-port block below the Windows
dynamic range; unset, any free port is used as before. Ported from
slice/ladder-run.
…odels

`python -m tests.live.sweep run` picks every OpenRouter model that takes
tools, holds 32k tokens and is priced, one per model_key, leaves out the
keys the 8-case ladder measured, and lists models from $3/M input as
assumed to pass. Each model runs smoke, then the PDF brief and the board
pack in lanes of their own; a smoke it fails stops it at 0 of 2, and a
single failed case is run once more. Provider and harness faults get two
retries and never count. A hard budget, a per-case dollar stop and time
limit, a RAM guard and an append-only attempts.jsonl make it safe to
leave running and to restart. It writes a summary per model and
sweep-results.json for the capability list's generator, and `report`
prints progress at any time. Classification, the RAM guard and the
process handling are ported from slice/ladder-run's matrix runner.
With opencode on by default, any SURFSENSE_LOCAL_OPENCODE_ENABLED other than
"1" turned it off: "true", "yes", and the empty string an unset CI variable
gives all built an installer without the agent, and stage.mjs blamed "=0" as
it removed the stage. Now unset or empty keeps the default, 1 and 0 decide,
and anything else stops the build. The off message prints the value it read.
The pinned opencode-darwin-arm64 arrives Developer ID signed by Anomaly
Innovations, with the hardened runtime, a secure timestamp and the five
entitlements Bun asks for. osx-sign force-signed it with the app's inherited
entitlements, which lack disable-executable-page-protection, and the release's
--version smoke barely runs Bun's JIT. mac.signIgnore now leaves opencode's
signature in place; rg arrives only ad-hoc signed and is still signed with the
app.

packaging.md also had Windows wrong: opencode.exe arrives Authenticode-signed
by its vendor and rg.exe unsigned, and a tag's build replaces both with
SurfSense's signature, so a dry run does not run an unsigned opencode.
Release CI ran check-stage.mjs on the packaged opencode only after
electron-builder, and on a tag `--publish always` has by then uploaded every
installer and stable*.yml to the draft release, so an opencode that signing
broke reached the draft first. An afterSign hook, scripts/opencode/after-sign.mjs,
now runs the same check once the app is signed, and on macOS notarized, and a
failure stops electron-builder before any target is built. macOS and Windows
call it on every build, signed or not; Linux signs nothing and skips it, so the
post-build CI step stays as its check.
The screening sweep stops a model that fails smoke, so a false failure
there costs it both scored cases. Ministral 14B answered "Monday,
November 16, 2026", the note's date, and failed only because the check
looked for "16 November". The check now takes either order; it still
needs the search, the date and a citation of the note.
In the pilot the RAM guard held a 3-lane sweep to one or two cases at
once, so an ETA at --lanes was three times too short. `report` now
divides by the lane-minutes ended since the runner started over the
minutes since, once ten minutes have passed. A re-run's note also names
the case once.
…etry time-outs

A 401 or 402 (a revoked key, no credits, "can only afford N tokens") reached
the model-failure fallthrough, so once the balance ran low every smoke after
it failed in seconds and its model was written as tested below 0/2, with
nothing to stop the run. Such a run is now an `account` attempt: never
counted, no try used, and the runner starts nothing new and says why in
runner.log, status.json and `report`. The same command resumes.

A 408 (as a status or a streamed code), httpx's ConnectTimeout,
WriteTimeout, PoolTimeout and CloseError, and OpenRouter's "No endpoints
found" are now provider faults, retried and never counted, so a Wi-Fi drop
or a model with no provider up is not held against the model.
sweep-results.json listed every model under `models`, and the capability
list's generator reads each row's counts as a measurement. An unresolved
smoke (passed 0, run 0) became a studio_only row noted as a failed smoke,
though the model never answered, and a pending row's null counts made the
whole file fail validation. Only models with a verdict are under `models`
now; unresolved, running and pending ones are under `unfinished`, which the
generator ignores. The file also gives its `date`, the day the last counted
run ended, for the rows' measured_on.
The screening sweep stops a model that fails smoke at 0 of 2, so the date
check must not turn on spelling. The last pattern still missed "November
16th" (no word boundary between "16" and "th"), "the 16th of November",
"Nov 16", "16 Nov", "2026-11-16", "16/11/2026" and lower case. It now takes
those, and still refuses the old yard's 20 November and other days. The
check is a function so a unit test can list the spellings.
…il on it

The board pack failed any answer without "assum", "I chose", "I went with"
and the like. In the screening pilot DeepSeek V4 Pro, an agent-level model
on the 8-case ladder, failed it twice that way: both answers made and named
a PDF and gave the reason ("ready to send as-is", with a Word draft
offered). With one re-run per split, wording alone made it "below". The
ladder branch already graded this rather than checked it; this ports only
that. The case still fails an answer that does not name the format, and
records `says_what_it_assumed` under result.json's new `metrics`.

A unit test drives the case on a scripted app with DeepSeek's answer.
The runner says one Ctrl-C stops new starts and waits for the cases in
flight, but each case shared the runner's console, so the terminal's Ctrl-C
reached every pytest child too. They died at once and were recorded as
harness faults, each using up one of the case's two retries. A case now
starts in its own process group (its own session elsewhere), so only the
runner takes the Ctrl-C; a second one still kills the cases through
kill_tree.

The test runs a one-lane sweep in a hidden console of its own and presses
Ctrl-C there while the case runs.
Nothing stopped a second `run` on the same --out while the first was
alive, though resuming is "run the same command". The second runner took
the first's live attempt folders for interrupted ones, charged their spend
again, started the same cases a second time and, on Windows, crashed
renaming a folder the first still held open. A runner now holds an OS lock
on runner.lock for its whole run; a second one raises SweepBusyError naming
the holder's pid, and the CLI exits with that message. The lock ends with
its process, however it ends.
…the file

Windows refuses to replace a file another process has open. The sweep's
runner reads each in-flight run's spend.json, so a charge written at that
moment raised PermissionError in the proxy's `finally`: it never reached the
run's ledger, which the per-case stop and the runner's in-flight estimate
both read. The same race with `report` reading sweep-results.json or
status.json would have crashed the runner mid-sweep. Both now write through
write_whole, which tries the replace again for up to a second.
… model

A row was found only by its canonical key, so Ollama's qwen3.8:27b,
a Q4_K_M .gguf or unsloth's GGUF repo of a tested model read as
untested. A second, looser key, used only to find a row, also drops a
path's folders, a file extension, quantisation tags, the instruct, it
and chat tags, reads name:tag as name-tag and hyphenates a letter
before a digit. Sizes and versions stay, so 27b never finds 14b and
3.8 never finds 3.6, and the list refuses two rows one looser key
would find.
… flagships

The tested list held only the ladder's 8-case rows. Two suites now add
rows after them: openrouter-screen v1, two cases after a smoke check,
at agent when both pass and studio_only otherwise with its counts; and
assumed, an expensive flagship listed at agent without runs, 0 of 0,
which capability_of reports with reason assumed instead of counts.

scripts/catalog_rows.py writes them from the sweep's
sweep-results.json, a model the list already names keeping its row by
its key or by a server's spelling. write_capability_list.py keeps them
when it rewrites the ladder's rows. The real results come later; a
fixture stands in.
…y a score

Agentic ran only for a model measured at the agent's bar, or one the
user opted in with "Try the agent". Now every model may choose it, and
only a technical gate keeps one out: no opencode staged, a stated no on
tool calls, or a stated window under 32,768 tokens. A catalog that says
nothing, and a local runtime that cannot be read, let a model in.

new_chat_modes() says what a new chat on the selected model may be:
agentic_allowed and the gate that blocks it, the default mode (Agentic
for a model measured at an agent level on a remote host, Basic
otherwise), the reason the switch shows beside Agentic, and the mode
remembered under chat_mode in the selection's settings. The selection
read carries it as capability.modes, in place of agent_trial, whose
route is gone; an agent_trial left on reads as a remembered Agentic.

POST /workspaces/{id}/chat/threads takes mode. Agentic asked for and
gated is a 409 with the gate's code, and a 503 agent_unavailable when
opencode does not start; neither leaves a thread. A mode asked for is
remembered as the model's default. An agent thread's next turn is held
to the technical gates alone. The developer switch now starts every
model's chats in Agentic and lifts the window floor.

The composer still calls the old route until the next commit.
A new chat's composer has a Basic (Q&A) / Agentic switch beside "+".
It starts at the selected model's default, follows the model when it
changes, and sends the pick as the new thread's mode; a pick holds per
model for the session, above the open workspace. Agentic says its
score, the untested warning, the note on a local copy or that a
flagship is assumed, beside the option and under the composer once
picked; a gated Agentic is disabled with its reason. An open chat's
switch shows the mode it keeps and offers the other only as "Start a
new chat in <mode> mode". A refused Agentic chat is worded by its code.

Settings drops "Try the agent": the chat model panel says the mode new
chats start in and that each chat picks its own, and the level labels
say how the model did in the Agentic tests. Strings in all ten
locales; Basic, Agentic and Q&A join the glossary.
ENABLED_BY_DEFAULT was false until a model passed the agent test, so
no installer staged opencode. Every chat model may now pick Agentic,
so it is true: the release's "Stage opencode" step and pnpm dev stage
opencode and ripgrep, and SURFSENSE_LOCAL_OPENCODE_ENABLED=0 still
leaves them out. The docs that said the agent is off in installers say
so no longer.
…ver its -chat variant

The sweep writes null counts for a model it stopped short of and keeps a
model a provider's errors left unresolved, and catalog_rows.py read every
row's counts as a measurement: a pending row failed the whole file, and an
unresolved smoke became a "Low Agentic score" row noted as a failed smoke.
A model now gets a row only with a verdict (status done, or no status and
every count), its level must agree with its counts, and it is dated by its
own measured_on before the sweep's date and --date. The rest are printed as
left out.

Stripping -chat folds openai/gpt-5.2-chat into gpt-5.2, and OpenRouter
lists the -chat one first, so the merge kept its 1-of-2 row and dropped
gpt-5.2's own 2-of-2. Between two of the sweep's rows the shorter key, the
model's own name, now wins, and the script says which row each one left
out collided with.
…assed

Settings showed an assumed row as "Passed SurfSense's Agentic tests.",
then "Not run: ..." twice, once as evidence and once as the row's English
note. It now says "Not run: SurfSense expects this expensive flagship
model to pass." once, in place of the level's line, the evidence and the
note.

A local copy of an assumed model read as "Passed on its full-size version"
beside Agentic, and as "measured through openrouter.ai" in Settings, for a
model never run anywhere. Both now say it is assumed; the copy still starts
in Basic.
The composer always sends a new thread's mode, and the API remembered any
mode sent, so the first chat on every model stored its default at that
moment as the user's choice. A model later screened as passing kept
starting in Basic, and every model used under the developer switch kept
starting in Agentic after it was turned off. ThreadCreate now takes
`remember`, which the composer sends only for a mode picked in the
switch; the mode is still always sent, so the chat opens as the switch
shows.

The remembered mode lived beside the selection and was cleared whenever
the slot took another model, so switching away and back lost it. It is
now one entry per provider, connection and name under `chat_modes`, kept
across a change of model; an old agent_trial is kept as its model's
Agentic.
Asking for Agentic can now be refused, by a gate with a 409 or, after up
to a minute's wait for opencode, with a 503. The composer had already
been cleared on send and was reset again on the way back to a new chat,
so the user had to retype the question to try again or switch to Basic.
A refused new chat now goes back with what was typed and its images,
which the composer takes up again after its reset.
The RAM guard learnt its lane size from peaks but never went below the
ladder's 1.6 GB, so a sweep told its cases peak near 1.2 GB still ran
two or three at once on a 16 GB desktop. A lane size given below that is
now the floor itself.
# Conflicts:
#	.github/workflows/release-local.yml
#	docs/architecture/agent.md
#	docs/architecture/packaging.md
#	docs/proposals/file-agent/04-runtime-and-packs.md
#	docs/proposals/file-agent/05-model-ladder-and-evals.md
#	docs/proposals/file-agent/06-product-shape.md
#	docs/proposals/file-agent/07-dev-setup.md
#	surfsense_local/electron/electron-builder.yml
#	surfsense_local/electron/scripts/opencode/enabled.mjs
#	surfsense_local/electron/scripts/opencode/enabled.test.mjs
The cases measure the agent, and a model off the tested list now starts
new chats in Basic (Q&A), so a live run asks for Agentic.
The agent can take a minute to start while the model picker stays open, and
the pick was written against whichever model the slot held once the thread
opened. new_thread_mode now returns the model's entry with the mode, and the
router remembers the pick under it.
The hand-off adds the sweep's screened and assumed rows to capabilities.json,
and tests counted the list's rows, used gpt-4o-mini as the untested model,
and took the sweep's own sizes for sizes the list does not name. They now
count the ladder's rows, use made-up ids, and match against the ladder's
rows alone.
…on's row

catalog_rows kept every listed row by key, so a gpt-5.2-chat listed while
gpt-5.2 had no verdict scored for gpt-5.2 for good, and an assumed flagship
could never be replaced by a screening; the sweep also skipped assumed keys.
The merge now keeps each model's strongest row (ladder, then screening, then
assumption; the shorter key within a suite), a variant waits while its own
model has no verdict, and the sweep chooses assumed flagships again. A below
row whose cases hit the per-case dollar cap says so in its note.
Installers now ship the agent and new chats on measured models start
Agentic, but attach and paste still took images there, and every turn with
one came back as a 409 that Retry repeated. The composer turns both off in
an Agentic chat and says why, and an image attached before a new chat was
switched to Agentic comes back with the question, opening nothing.
A local model's tool calls and window are read only when a chat starts, so
the switch kept offering Agentic after a 409 and every send asked again.
The refused gate is now held per model for the session: Agentic is disabled
with its reason and the next chat starts in Basic (Q&A).
…s Agentic

Proposals 01, 03 and 07 and the roadmap still kept agent threads behind
the developer switch or a tested list. The switch's own wording said it
starts every model's new chats in Agentic, but a mode the user picked for a
model still wins, and no opencode still keeps Agentic out. 07-dev-setup now
says which models start Agentic and lists the composer's Basic (Q&A) as the
likeliest reason a thread answers as a chat.
…ssume named flagships, and clear a lane's leftovers

A reasoning flagship that spends more than a case's stop was counted as
failing, so a model too dear to test started chats in Basic. Its first
capped case now ends it as assumed to pass, like the dearest flagships,
and --assume names more to take that way without running them.

A case that died left its opencode holding the lane's database, and every
later case in that lane failed before it started until six harness faults
stopped the sweep. A lane's leftovers are now stopped before each case and
after it.
They bound lanes 3 and 4's real ports, so they failed whenever a sweep ran
beside them.
# Conflicts:
#	surfsense_local/backend/tests/live/sweep/test_model_list.py
…1 assumed flagships

The sweep of 7 Oct 2026 ran every OpenRouter model that calls tools, holds
32,768 tokens and costs under $3/M of input on the smoke case, then the
PDF brief and the board pack: 99 passed both and start chats in Agentic,
109 start in Basic (Q&A) with Agentic offered. Models from $3/M of input,
the reasoning flagships whose output costs $10/M or more, and those whose
case went past the $3 stop are assumed to pass. 11 models the providers
kept rate-limiting stay untested. It cost $60.73.
OpenAI's models fill every field a tool lists, so a render with no
document to continue and no template arrived with artifact_id 0 and
template_source_id 0. The tool asked for source 0, refused it as not
selected, and the model sent the same call until the turn ran out: in
the OpenRouter sweep no OpenAI model rendered a document, and two Grok
models hit the same refusal. Ids count from 1, so an optional *_id of 0
now names nothing.
…oken

The settle helpers waited 20 ms, under the 32 ms the run store now waits
between text notices, so a token was sometimes read before it was told
and the tests failed under load.
…ad failed

With an optional id of 0 left out, 18 OpenAI and Grok models ran the
screening again: o3, GPT-4.1, GPT-5 mini, GPT-5.4 mini, GPT-5.1 Codex
mini, GPT-5.6 Luna Pro, GPT-6 Luna and both Grok models now pass both
cases and start chats in Agentic. 106 screened models are now agent, 102
below.
The list is written by its generators only, which keep the ladder's
source; the sweep is described in model-capabilities.md.
@vercel

vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
surf-sense-frontend Ready Ready Preview Oct 8, 2026 8:57pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository: MODSetter/SurfSense/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 76ac9c88-e7f3-4d3b-a662-c86e9c395241
📥 Commits

Reviewing files that changed from the base of the PR and between 91b0e09 and 4330d89.

⛔ Files ignored due to path filters (1)
  • surfsense_local/backend/tests/live/sweep/fixtures/thread_opened_as_a_chat/pytest.log is excluded by !**/*.log
📒 Files selected for processing (162)
  • .agents/skills/translate/glossary.md
  • .github/workflows/release-local.yml
  • docs/ROADMAP.md
  • docs/architecture/agent.md
  • docs/architecture/chat.md
  • docs/architecture/chatgpt-subscription.md
  • docs/architecture/model-capabilities.md
  • docs/architecture/overview.md
  • docs/architecture/packaging.md
  • docs/proposals/agent/01-which-engine.md
  • docs/proposals/agent/03-opencode.md
  • docs/proposals/file-agent/01-sources-and-folders.md
  • docs/proposals/file-agent/02-skills-and-engines.md
  • docs/proposals/file-agent/03-editable-artifacts.md
  • docs/proposals/file-agent/04-runtime-and-packs.md
  • docs/proposals/file-agent/05-model-ladder-and-evals.md
  • docs/proposals/file-agent/06-product-shape.md
  • docs/proposals/file-agent/07-create-and-edit-mvp.md
  • docs/proposals/file-agent/07-dev-setup.md
  • docs/proposals/file-agent/README.md
  • plans/community-local/file-agent-strategy.md
  • surfsense_local/backend/modules/agent/engine_choice.py
  • surfsense_local/backend/modules/agent/tool_endpoint/offered_tools.py
  • surfsense_local/backend/modules/chat/router.py
  • surfsense_local/backend/modules/chat/schemas.py
  • surfsense_local/backend/modules/llm/capability/agent_gate.py
  • surfsense_local/backend/modules/llm/capability/agent_trial.py
  • surfsense_local/backend/modules/llm/capability/match_key.py
  • surfsense_local/backend/modules/llm/capability/measured/capabilities.json
  • surfsense_local/backend/modules/llm/capability/measured/loader.py
  • surfsense_local/backend/modules/llm/capability/measured/schema.py
  • surfsense_local/backend/modules/llm/capability/modes/__init__.py
  • surfsense_local/backend/modules/llm/capability/modes/mode.py
  • surfsense_local/backend/modules/llm/capability/modes/new_chat.py
  • surfsense_local/backend/modules/llm/capability/modes/remembered.py
  • surfsense_local/backend/modules/llm/capability/read.py
  • surfsense_local/backend/modules/llm/capability/resolve.py
  • surfsense_local/backend/modules/llm/capability/router.py
  • surfsense_local/backend/modules/llm/capability/schemas.py
  • surfsense_local/backend/modules/llm/router.py
  • surfsense_local/backend/modules/llm/selection.py
  • surfsense_local/backend/scripts/capability_list/merge.py
  • surfsense_local/backend/scripts/capability_list/rows.py
  • surfsense_local/backend/scripts/capability_list/shipped.py
  • surfsense_local/backend/scripts/capability_list/sweep_input.py
  • surfsense_local/backend/scripts/capability_list/sweep_rows.py
  • surfsense_local/backend/scripts/catalog_rows.py
  • surfsense_local/backend/scripts/write_capability_list.py
  • surfsense_local/backend/shared/config.py
  • surfsense_local/backend/tests/fixtures/sweep_results_sample.json
  • surfsense_local/backend/tests/integration/agent/test_agent_threads.py
  • surfsense_local/backend/tests/integration/agent/test_document_tools.py
  • surfsense_local/backend/tests/integration/agent/test_engine_choice.py
  • surfsense_local/backend/tests/integration/agent/test_thread_modes.py
  • surfsense_local/backend/tests/integration/llm/test_capability_routes.py
  • surfsense_local/backend/tests/live/conftest.py
  • surfsense_local/backend/tests/live/lane_ports.py
  • surfsense_local/backend/tests/live/live_agent.py
  • surfsense_local/backend/tests/live/run_folder.py
  • surfsense_local/backend/tests/live/spend_ledger.py
  • surfsense_local/backend/tests/live/sweep/__main__.py
  • surfsense_local/backend/tests/live/sweep/attempt.py
  • surfsense_local/backend/tests/live/sweep/attempt_log.py
  • surfsense_local/backend/tests/live/sweep/budget.py
  • surfsense_local/backend/tests/live/sweep/ctrl_c_sweep.py
  • surfsense_local/backend/tests/live/sweep/fixtures/assertion_failure/20261005T000000Z-run/cost.json
  • surfsense_local/backend/tests/live/sweep/fixtures/assertion_failure/20261005T000000Z-run/frames.json
  • surfsense_local/backend/tests/live/sweep/fixtures/assertion_failure/20261005T000000Z-run/model-requests.json
  • surfsense_local/backend/tests/live/sweep/fixtures/assertion_failure/20261005T000000Z-run/result.json
  • surfsense_local/backend/tests/live/sweep/fixtures/context_overflow/20261005T000000Z-run/cost.json
  • surfsense_local/backend/tests/live/sweep/fixtures/context_overflow/20261005T000000Z-run/frames.json
  • surfsense_local/backend/tests/live/sweep/fixtures/context_overflow/20261005T000000Z-run/model-requests.json
  • surfsense_local/backend/tests/live/sweep/fixtures/context_overflow/20261005T000000Z-run/result.json
  • surfsense_local/backend/tests/live/sweep/fixtures/provider_ended_the_turn/20261005T000000Z-run/cost.json
  • surfsense_local/backend/tests/live/sweep/fixtures/provider_ended_the_turn/20261005T000000Z-run/frames.json
  • surfsense_local/backend/tests/live/sweep/fixtures/provider_ended_the_turn/20261005T000000Z-run/model-requests.json
  • surfsense_local/backend/tests/live/sweep/fixtures/provider_ended_the_turn/20261005T000000Z-run/result.json
  • surfsense_local/backend/tests/live/sweep/fixtures/recovered_blip_then_content_failure/20261005T000000Z-run/cost.json
  • surfsense_local/backend/tests/live/sweep/fixtures/recovered_blip_then_content_failure/20261005T000000Z-run/frames.json
  • surfsense_local/backend/tests/live/sweep/fixtures/recovered_blip_then_content_failure/20261005T000000Z-run/model-requests.json
  • surfsense_local/backend/tests/live/sweep/fixtures/recovered_blip_then_content_failure/20261005T000000Z-run/result.json
  • surfsense_local/backend/tests/live/sweep/fixtures/thread_opened_as_a_chat/20261005T000000Z-run/cost.json
  • surfsense_local/backend/tests/live/sweep/fixtures/thread_opened_as_a_chat/20261005T000000Z-run/frames.json
  • surfsense_local/backend/tests/live/sweep/fixtures/thread_opened_as_a_chat/20261005T000000Z-run/model-requests.json
  • surfsense_local/backend/tests/live/sweep/fixtures/thread_opened_as_a_chat/20261005T000000Z-run/result.json
  • surfsense_local/backend/tests/live/sweep/model_list.py
  • surfsense_local/backend/tests/live/sweep/plan.py
  • surfsense_local/backend/tests/live/sweep/process.py
  • surfsense_local/backend/tests/live/sweep/ram_guard.py
  • surfsense_local/backend/tests/live/sweep/report.py
  • surfsense_local/backend/tests/live/sweep/results.py
  • surfsense_local/backend/tests/live/sweep/retry_rule.py
  • surfsense_local/backend/tests/live/sweep/runner.py
  • surfsense_local/backend/tests/live/sweep/runner_lock.py
  • surfsense_local/backend/tests/live/sweep/stand_in_case.py
  • surfsense_local/backend/tests/live/sweep/test_attempt.py
  • surfsense_local/backend/tests/live/sweep/test_budget.py
  • surfsense_local/backend/tests/live/sweep/test_model_list.py
  • surfsense_local/backend/tests/live/sweep/test_plan.py
  • surfsense_local/backend/tests/live/sweep/test_ram_guard.py
  • surfsense_local/backend/tests/live/sweep/test_reap_lane.py
  • surfsense_local/backend/tests/live/sweep/test_report.py
  • surfsense_local/backend/tests/live/sweep/test_results.py
  • surfsense_local/backend/tests/live/sweep/test_runner.py
  • surfsense_local/backend/tests/live/test_board_pack.py
  • surfsense_local/backend/tests/live/test_board_pack_wording.py
  • surfsense_local/backend/tests/live/test_lane_ports.py
  • surfsense_local/backend/tests/live/test_run_folder.py
  • surfsense_local/backend/tests/live/test_smoke.py
  • surfsense_local/backend/tests/live/test_smoke_move_day.py
  • surfsense_local/backend/tests/live/test_spend_ledger.py
  • surfsense_local/backend/tests/live/whole_file.py
  • surfsense_local/backend/tests/unit/agent/test_placeholder_ids.py
  • surfsense_local/backend/tests/unit/llm/capability/test_capability_of.py
  • surfsense_local/backend/tests/unit/llm/capability/test_match_key.py
  • surfsense_local/backend/tests/unit/llm/capability/test_measured_schema.py
  • surfsense_local/backend/tests/unit/llm/capability/test_new_chat_modes.py
  • surfsense_local/backend/tests/unit/scripts/test_capability_list.py
  • surfsense_local/backend/tests/unit/scripts/test_catalog_rows.py
  • surfsense_local/electron/build/entitlements.mac.plist
  • surfsense_local/electron/electron-builder.yml
  • surfsense_local/electron/scripts/opencode/after-sign.mjs
  • surfsense_local/electron/scripts/opencode/after-sign.test.mjs
  • surfsense_local/electron/scripts/opencode/check-stage.mjs
  • surfsense_local/electron/scripts/opencode/check-stage.test.mjs
  • surfsense_local/electron/scripts/opencode/enabled.mjs
  • surfsense_local/electron/scripts/opencode/enabled.test.mjs
  • surfsense_local/electron/scripts/opencode/sign-mac.test.mjs
  • surfsense_local/electron/scripts/opencode/stage.mjs
  • surfsense_local/frontend/src/features/chat/api.ts
  • surfsense_local/frontend/src/features/chat/chat-composer.tsx
  • surfsense_local/frontend/src/features/chat/chat-images.test.tsx
  • surfsense_local/frontend/src/features/chat/composer-add-menu.tsx
  • surfsense_local/frontend/src/features/chat/image-attachments.ts
  • surfsense_local/frontend/src/features/chat/model-picker.test.tsx
  • surfsense_local/frontend/src/features/chat/modes/agentic-note.tsx
  • surfsense_local/frontend/src/features/chat/modes/mode-picker.test.tsx
  • surfsense_local/frontend/src/features/chat/modes/mode-picker.tsx
  • surfsense_local/frontend/src/features/chat/modes/mode-text.ts
  • surfsense_local/frontend/src/features/chat/modes/new-chat-mode-provider.tsx
  • surfsense_local/frontend/src/features/chat/modes/new-chat-mode.ts
  • surfsense_local/frontend/src/features/chat/thread-panel.tsx
  • surfsense_local/frontend/src/features/chat/use-chat-runtime.test.tsx
  • surfsense_local/frontend/src/features/chat/use-chat-runtime.ts
  • surfsense_local/frontend/src/features/dashboard/dashboard-page.test.tsx
  • surfsense_local/frontend/src/features/dashboard/dashboard-page.tsx
  • surfsense_local/frontend/src/features/dashboard/dashboard-streaming.test.tsx
  • surfsense_local/frontend/src/features/models/capability/api.ts
  • surfsense_local/frontend/src/features/models/capability/capability-label.ts
  • surfsense_local/frontend/src/features/models/capability/model-capability.test.tsx
  • surfsense_local/frontend/src/features/models/capability/model-capability.tsx
  • surfsense_local/frontend/src/features/models/capability/use-agent-trial.ts
  • surfsense_local/frontend/translations/de.json
  • surfsense_local/frontend/translations/en.json
  • surfsense_local/frontend/translations/es.json
  • surfsense_local/frontend/translations/fr.json
  • surfsense_local/frontend/translations/hi.json
  • surfsense_local/frontend/translations/ja.json
  • surfsense_local/frontend/translations/ko.json
  • surfsense_local/frontend/translations/pt-BR.json
  • surfsense_local/frontend/translations/ru.json
  • surfsense_local/frontend/translations/zh-CN.json
 __________________________________________________________________________
< This function returns three different shapes. It's basically modern art. >
 --------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MODSetter
MODSetter merged commit 2f5e1a2 into dev Oct 8, 2026
22 of 24 checks passed
MODSetter pushed a commit that referenced this pull request Oct 24, 2026
ADR 0044 records that Basic and Agentic are chosen per chat and a measured score sets only the default, as built in #2207. The planned 0044 entries in the file-agent proposal now point at it. Refs #2220.

This branch was successfully deployed

1 active deployment
Preview — 4330d897 Deployed Oct 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant