Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
2327494
docs(file-agent): propose the file agent and plan its first slice
MODSetter Oct 4, 2026
82a671e
feat(worker): run document scripts in a separate process
MODSetter Oct 4, 2026
77ecca4
feat(ingest): keep each source's figures beside its original
MODSetter Oct 4, 2026
6a133d1
feat(studio): build versioned Word and PDF documents from scripts
MODSetter Oct 4, 2026
4e60719
feat(studio): show document versions and open an agent's render in St…
MODSetter Oct 4, 2026
d5493f5
test(agent): start the test opencode with the system variables Window…
MODSetter Oct 4, 2026
2c50067
feat(agent): draw a document version's pages so the agent can check i…
MODSetter Oct 4, 2026
a050d22
feat(agent): make Word and PDF documents as versioned scripts, with n…
MODSetter Oct 4, 2026
1bf8a01
test(agent): try the document flow live against Claude Sonnet on a sp…
MODSetter Oct 4, 2026
fde2c86
feat(llm): refresh the remote model manifest and list what a refresh …
MODSetter Oct 4, 2026
9d63463
fix(studio): never retry a script document that failed
MODSetter Oct 4, 2026
0b27540
fix(agent): let only Electron answer Word snapshots, and show Word fi…
MODSetter Oct 4, 2026
e7a8a41
feat(agent): show the agent its previews and source figures, and keep…
MODSetter Oct 4, 2026
d3ef9fc
test(agent): give the live Word printer the snapshot key Electron holds
MODSetter Oct 4, 2026
bede4c6
docs(file-agent): describe the create-and-edit slice as built and how…
MODSetter Oct 4, 2026
0ae985a
fix(agent): keep Studio's write lock short and tell a script's failur…
MODSetter Oct 4, 2026
5ee7737
fix(studio): keep a working document in view when its latest edit fai…
MODSetter Oct 4, 2026
3c417d2
test(agent): try five more live document cases, and have the agent ch…
MODSetter Oct 4, 2026
b61d458
fix(agent): say whether a render created its document, keep a reply's…
MODSetter Oct 4, 2026
c82d951
test(agent): charge a cut-off live request's images by their pixels, …
MODSetter Oct 4, 2026
ebabce0
docs(file-agent): list the five new live cases, their costs and how t…
MODSetter Oct 4, 2026
6fd13c5
feat(agent): keep an agent turn to its ticked sources and show which …
MODSetter Oct 4, 2026
61e0139
feat(studio): draft Word and PDF as Markdown on small models or a run…
MODSetter Oct 5, 2026
556241f
feat(studio): refine a Word document or PDF from the artifact panel
MODSetter Oct 5, 2026
c88df4e
docs(studio): describe the Word and PDF paths, their kept specs and R…
MODSetter Oct 5, 2026
5313005
test(agent): run the live cases on a chosen model and provider, each …
MODSetter Oct 5, 2026
8463f45
test(agent): let the demo's Word file start at v2 when its first rend…
MODSetter Oct 5, 2026
ac73dc5
docs(file-agent): report the first model ladder runs, Opus 5.5 and Ha…
MODSetter Oct 5, 2026
00ed2a7
feat(sources): keep every source in a folder and let a chat or Studio…
MODSetter Oct 5, 2026
6aefa9d
feat(sources): show sources as a folder tree with tick boxes that set…
MODSetter Oct 5, 2026
1796ca8
docs(architecture): describe folders, the managed source root and ser…
MODSetter Oct 5, 2026
95299ce
Merge slice/folders: folders, server-side source scope and the source…
MODSetter Oct 5, 2026
6d882c0
fix(studio): keep Refine to Studio's own Word and PDF, count a model …
MODSetter Oct 5, 2026
2d3338a
fix(agent): tell the agent a ticked folder is still being read, not t…
MODSetter Oct 5, 2026
6359778
test(agent): hold only a model that reads images to the page previews
MODSetter Oct 5, 2026
2de7cdb
docs(file-agent): screen eight open models on the ladder through Open…
MODSetter Oct 5, 2026
a04c484
feat(agent): make PowerPoint decks and Excel workbooks, start from a …
MODSetter Oct 5, 2026
261742d
feat(agent): print decks for the agent's previews and source pages, a…
MODSetter Oct 5, 2026
3ccb235
docs(file-agent): describe decks, workbooks, templates from sources a…
MODSetter Oct 5, 2026
9daaa65
build(backend): name openpyxl, which reads back every workbook a docu…
MODSetter Oct 5, 2026
af86482
Merge slice/ladder: the live harness that runs any model and provider
MODSetter Oct 5, 2026
7d501bc
Merge slice/agent-formats into the per-thread folders base
MODSetter Oct 5, 2026
892a87f
Merge slice/free-tier: Studio's Word and PDF on two paths, and Refine
MODSetter Oct 5, 2026
fe6b6e2
fix(studio): let a refine place only the figures its draft was ground…
MODSetter Oct 5, 2026
dbff5d8
docs(file-agent): run Gemma 4 31B and Qwen3.5-9B past the gate, and g…
MODSetter Oct 5, 2026
a2cf0de
Merge slice/agent-formats: decks, workbooks, templates from sources a…
MODSetter Oct 5, 2026
bc5b4b9
fix(agent): name the source whose pages a step looked at, from the tu…
MODSetter Oct 5, 2026
c6b77df
docs(agent): say the source-pages tool and a render's template keep t…
MODSetter Oct 5, 2026
be661cc
feat(agent): give each agent chat its own folder of the sources it wa…
MODSetter Oct 5, 2026
cf80ccf
feat(chat): offer a new chat when an older agent chat is refused, and…
MODSetter Oct 5, 2026
004114e
docs(architecture): describe per-chat agent folders, the shared text …
MODSetter Oct 5, 2026
2a8371c
Merge slice/thread-folders: each agent chat works in its own folder o…
MODSetter Oct 5, 2026
0ba5080
fix(agent): refuse every tool call from an address naming no agent th…
MODSetter Oct 5, 2026
6a36744
feat(agent): send a render's and a source's pages to the model as ima…
MODSetter Oct 5, 2026
d7a7535
test(agent): count a page as checked only when its own inline image r…
MODSetter Oct 5, 2026
eb9bed8
docs(file-agent): describe inline page previews, and report the ladde…
MODSetter Oct 5, 2026
727cfdf
fix(agent): refuse to render a next version of a Studio document that…
MODSetter Oct 5, 2026
a154a57
fix(agent): name the source a step looked at the pages of on a turn w…
MODSetter Oct 5, 2026
5d916af
fix(agent): cap live thread instances, stop a turn after three failed…
MODSetter Oct 5, 2026
ab5e4d2
fix(sources): finish folder deletes the app stopped partway, and take…
MODSetter Oct 5, 2026
3135231
docs(architecture): describe the instance cap, the render stop, path …
MODSetter Oct 5, 2026
e039ffb
fix(studio): leave Studio's Word and PDF to Studio, and let Huey retr…
MODSetter Oct 5, 2026
ed36478
feat(llm): give each chat model a measured capability level that pick…
MODSetter Oct 5, 2026
a87ae2f
feat(models): label each chat model's measured level and offer "Try t…
MODSetter Oct 5, 2026
411c4e4
docs(architecture): describe measured model capability and what the e…
MODSetter Oct 5, 2026
eec9ad6
Merge slice/inline-previews: a render's and a source's pages reach th…
MODSetter Oct 5, 2026
9384213
Merge slice/ladder: the small-tier runs and the failure groups
MODSetter Oct 5, 2026
8cc08d9
Merge slice/hardening: the render stop, the instance cap, path budget…
MODSetter Oct 5, 2026
612c260
fix(agent): refuse a same-title render without artifact_id, end the s…
MODSetter Oct 5, 2026
f185163
fix(agent): never dispose an instance twice, and let a turn wait out …
MODSetter Oct 5, 2026
19a8c1a
Merge slice/capability: each model's measured level picks its engine …
MODSetter Oct 5, 2026
2069de8
fix(agent): read_document continues from the agent's newest version, …
MODSetter Oct 5, 2026
ae8b9be
docs(agent): say which models get the agent once opencode is staged, …
MODSetter Oct 5, 2026
94d23d8
test(agent): an agent thread's next turn follows its model's level an…
MODSetter Oct 5, 2026
cf2b795
test(agent): refusing Studio's document neither counts as a failed ru…
MODSetter Oct 5, 2026
fe34a6e
Merge dev into dev_mod: sync with 43 contributor commits
MODSetter Oct 5, 2026
b1e546c
fix(studio): a Refine refused for want of a chat model carries needs_…
MODSetter Oct 5, 2026
3468a3d
docs(chat): a refused image turn stores no message, but its sent scope
MODSetter Oct 5, 2026
d8af6f9
docs(studio): a script document's Regenerate is not refused for a mis…
MODSetter Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .agents/skills/translate/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ SurfSense, Studio, llama.cpp, GGUF, Hugging Face, API, MCP, and every model and
| privacy | プライバシー | 프라이버시 | 隐私 | गोपनीयता | Datenschutz † | privacidad | confidentialité | privacidade | конфиденциальность |
| source (an added file or folder) | ソース | 소스 | 来源 | स्रोत | Quelle | fuente | source | fonte | источник |
| note (a source the user writes) | メモ | 메모 | 笔记 | नोट | Notiz | nota | note | nota | заметка |
| folder (holds sources in the sources panel) | フォルダー | 폴더 | 文件夹 | फ़ोल्डर | Ordner | carpeta | dossier | pasta | папка |
| Library (the top of a workspace's folders) | ライブラリ | 라이브러리 | 资料库 | लाइब्रेरी | Bibliothek | Biblioteca | Bibliothèque | Biblioteca | Библиотека |
| thinking (a model reasoning before it answers) | 考え中 / 考えました | 생각 | 思考 | सोचना | nachdenken | pensar | réflexion | pensar | размышления |
| document | ドキュメント | 문서 | 文档 | दस्तावेज़ | Dokument | documento | document | documento | документ |
| chat | チャット | 채팅 | 对话 | चैट | Chat | chat | chat | chat | чат |
Expand All @@ -37,6 +39,8 @@ SurfSense, Studio, llama.cpp, GGUF, Hugging Face, API, MCP, and every model and
| speech (a model reading text aloud) | 音声合成 | 음성 | 语音 | वॉइस | Sprache | voz | voix | voz | речь |
| workspace | ワークスペース | 워크스페이스 | 工作区 | वर्कस्पेस | Workspace | espacio de trabajo | espace de travail | Workspace | рабочая область |
| artifact (a Studio output) | 生成物 | 생성물 | 产物 | आर्टिफ़ैक्ट | Artefakt | artefacto | artefact | artefato | артефакт |
| version (one of a document’s versions, shown as v1, v2) | バージョン | 버전 | 版本 | वर्ज़न | Version | versión | version | versão | версия |
| script (the code the agent writes to make a document) | スクリプト | 스크립트 | 脚本 | स्क्रिप्ट | Skript | script | script | script | скрипт |
| chunk (a cited passage) | チャンク | 청크 | 片段 | चंक | Abschnitt | fragmento | extrait | trecho | фрагмент |
| citation | 引用 | 인용 | 引用 | उद्धरण | Quellenangabe | cita | citation | citação | цитата |
| Model setup | モデル設定 | 모델 설정 | 模型设置 | मॉडल सेटअप | Modell-Einrichtung | Ajustes del modelo | Configuration du modèle | Configuração de modelos | Настройка модели |
Expand Down
3 changes: 2 additions & 1 deletion docs/adr/0010-studio-builders-not-sandboxes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The hosted Studio lets the model write code, runs it in a network-denied sandbox

## Where the code stands

- DOCX, PPTX, XLSX and PDF do not follow the rule. Their prompts ask the model to "write one standalone Python script", and [`worker/studio/office/runner.py`](../../surfsense_local/backend/worker/studio/office/runner.py) runs the reply with `exec()` in the worker process, unsandboxed, bounded by a 120-second timeout that cannot hard-kill a runaway thread. The module's `ponytail:` comment names that ceiling, and an out-of-process sandbox runner as the upgrade path. For these four formats the risk is back at arbitrary code with the worker's privileges.
- PPTX and XLSX do not follow the rule. Their prompts ask the model to "write one standalone Python script", and [`worker/studio/office/runner.py`](../../surfsense_local/backend/worker/studio/office/runner.py) runs the reply with `exec()` in the worker process, unsandboxed, bounded by a 120-second timeout that cannot hard-kill a runaway thread. The module's `ponytail:` comment names that ceiling, and an out-of-process sandbox runner as the upgrade path. For these two formats the risk is back at arbitrary code with the worker's privileges.
- DOCX and PDF follow the rule on small models, which write Markdown for committed builders, and run a strong model's script in the out-of-process runner of [ADR 0039](0039-document-scripts-run-without-approval.md) ([studio](../architecture/studio.md#word-and-pdf)).
- The other eight formats (summary, mind map, flashcards, quiz, HTML, podcast, image and infographic) execute nothing the model wrote.
- The HTML viewer renders into `<iframe sandbox="allow-scripts allow-popups">` ([`features/studio/viewers/html-viewer.tsx`](../../surfsense_local/frontend/src/features/studio/viewers/html-viewer.tsx)).
2 changes: 1 addition & 1 deletion docs/adr/0028-model-written-code-runs-with-approval.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 0028: Model-written code may run on the user's machine without a sandbox, and the agent asks before each shell command

- **Status:** Accepted
- **Status:** Accepted; the agent's shell asking before each command and Studio's Office code running inside the worker are superseded by [ADR 0039](0039-document-scripts-run-without-approval.md)
- **Date:** 2026-09-23
- **Supersedes:** the rule in [ADR 0010](0010-studio-builders-not-sandboxes.md) that no model-written code runs
- **Source:** [Office runner L32–34](https://github.com/MODSetter/SurfSense/blob/ecec636f1746bcdb523562c148d0fa298300290c/surfsense_local/backend/worker/studio/office/runner.py#L32-L34), [opencode permission defaults L119–136](https://github.com/anomalyco/opencode/blob/545f51d26cc39a907d2867492d498d9607ea5fa4/packages/opencode/src/agent/agent.ts#L119-L136), [opencode permission reply L31](https://github.com/anomalyco/opencode/blob/545f51d26cc39a907d2867492d498d9607ea5fa4/packages/opencode/src/server/routes/instance/httpapi/groups/permission.ts#L31)
Expand Down
41 changes: 41 additions & 0 deletions docs/adr/0039-document-scripts-run-without-approval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ADR 0039: Model-written document scripts run without approval in SurfSense's script runner, and the agent's general shell is off

- **Status:** Accepted
- **Date:** 2026-10-03
- **Supersedes:** in part [ADR 0028](0028-model-written-code-runs-with-approval.md): the agent no longer asks before each shell command, because it has no shell, and Studio's Office code no longer runs inside the worker process
- **Source:** [agent permissions L27–41](https://github.com/MODSetter/SurfSense/blob/0847e12f723394ff8343e586c074d6ebb3541fed/surfsense_local/backend/modules/agent/opencode_config.py#L27-L41), [Office runner L34](https://github.com/MODSetter/SurfSense/blob/0847e12f723394ff8343e586c074d6ebb3541fed/surfsense_local/backend/worker/studio/office/runner.py#L34)

## Context

Under ADR 0028 the agent asks before every shell command and SurfSense never answers "always". Creating a Word file and refining it over a few turns takes several commands per turn, so the user would answer a dialog for each one. The packaged app also gives the shell nothing to run: it ships no Python or Node on the user's `PATH`.

Formats like Word and PDF need code to get tables, styles and layout right, and a strong model writes that code well. Studio already runs model-written Python for its four Office formats, without asking, with `exec()` on a thread inside the worker, where its 120-second limit cannot stop it.

The user's sources are untrusted input, and a document can carry instructions aimed at the model.

## Decision

- A model-written document script runs without asking the user when it runs through SurfSense's script runner. The runner starts a separate process from SurfSense's own Python: the worker binary in a script mode when packaged, the backend's environment in development. It kills the process at its time limit, gives it an empty working folder and one output path, and offers only the libraries SurfSense ships.
- The script that made a version is kept with that version, so the next edit changes the script and runs it again.
- The agent's `bash` permission becomes `deny`. Document scripts reach the runner through a SurfSense tool, and the agent runs no other program.
- Studio's Office formats move onto the runner. A format whose model writes Markdown or JSON keeps a committed builder, as ADR 0010 describes.
- Which models may write document scripts follows from what the model is measured or confirmed to do, not from this ADR.

## Consequences

- No approval dialog appears for document work, and the user can see the script behind each version.
- The runner is not a sandbox. A script runs with the user's privileges and can read files and open network connections, so a source that steers the model can reach the machine through a script. That is accepted for development and early users. It must be revisited before SurfSense is marketed to security, legal or government buyers.
- Blocking network access and child processes inside a script, and a real sandbox, are later work.
- With the shell denied, the agent works through opencode's own `read`, `grep`, `glob` and file tools and SurfSense's tools.

## Where the code stands

The runner and the agent's side are built on `dev_mod` ([the create-and-edit slice](../proposals/file-agent/07-create-and-edit-mvp.md)):

- **The runner.** [`worker/document_script/`](../../surfsense_local/backend/worker/document_script/) runs a script in a process of its own: the frozen worker binary in its `--run-document-script` mode when packaged, [`worker.py`](../../surfsense_local/backend/worker.py) on the backend's Python in development. It gives the script an empty folder, one output path, the images the call names and an environment built from a few system variables, with no key or secret. At 120 seconds, or on a cancel, it kills the script and everything it started: a job object holds them on Windows, a process group elsewhere ([studio](../architecture/studio.md#script-documents)).
- **Kept with its version.** An agent's Word document or PDF is a script document: its script is the artifact's spec, each edit is a new version, and the next edit reads and changes that script.
- **No shell.** `bash` is `deny` in [`opencode_config.py`](../../surfsense_local/backend/modules/agent/opencode_config.py), and the agent reaches the runner only through `surfsense_render_document` ([agent](../architecture/agent.md#documents-the-agent-makes)).

- **Studio's Word and PDF.** A strong model's script runs through the runner and is kept as the version's spec; a small model writes Markdown that committed builders render ([studio](../architecture/studio.md#word-and-pdf)).

Not yet: Studio's PowerPoint and Excel still run with `exec()` in [`office/runner.py`](../../surfsense_local/backend/worker/studio/office/runner.py), and nothing blocks a script's network access or the processes it starts.
3 changes: 2 additions & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ The Date is the day the decision was first written down. Source links are permal
| 0025 | [The hosted scraper API client ships as a paid plugin whose source lives in this repo](0025-scraper-client-as-paid-plugin.md) | Accepted |
| 0026 | [Curated models are ordered by their position in the manifest, with no score](0026-curated-order-is-list-position.md) | Accepted, in part revised by the model catalog proposal |
| 0027 | [Egress consent is per host, so model search and downloads share one](0027-egress-consent-per-host.md) | Accepted |
| 0028 | [Model-written code may run on the user's machine without a sandbox, and the agent asks before each shell command](0028-model-written-code-runs-with-approval.md) | Accepted |
| 0028 | [Model-written code may run on the user's machine without a sandbox, and the agent asks before each shell command](0028-model-written-code-runs-with-approval.md) | Accepted, in part superseded by 0039 |
| 0029 | [Interface text lives in ICU MessageFormat catalogs, one flat JSON file per language](0029-icu-translation-catalogs.md) | Accepted |
| 0030 | [FormatJS renders interface text from precompiled, bundled catalogs, with no network at build or run time](0030-formatjs-renders-interface-text.md) | Accepted |
| 0031 | [Ranking blends the two legs on absolute scales, keyword strength being term coverage rather than BM25](0031-ranking-blends-absolute-leg-scores.md) | Accepted, amended by 0032 and 0033 |
Expand All @@ -73,3 +73,4 @@ The Date is the day the decision was first written down. Source links are permal
| 0036 | [The index records which embedder built it, and the embedder is fixed once per install](0036-the-index-records-its-embedder.md) | Accepted |
| 0037 | [Curated embedders are a fourth engine in the local catalog, and embedding is a type, never a slot](0037-embedding-is-a-type-not-a-slot.md) | Accepted |
| 0038 | [A ChatGPT plan is a connection signed in through OpenAI's open-source flow, not the Codex CLI's client](0038-chatgpt-plans-sign-in-through-openai-not-codex.md) | Accepted |
| 0039 | [Model-written document scripts run without approval in SurfSense's script runner, and the agent's general shell is off](0039-document-scripts-run-without-approval.md) | Accepted |
Loading
Loading