73 Smartsheet tools · 80 formulas · 7 LLM providers · 445 green tests · 81 in-app prompts · Full voice control · One conversation.
Chrome extension: the same Controller runs in Chrome’s side panel beside Smartsheet (details & install).
A community-built AI agent that speaks the Smartsheet API fluently — built for developers and power-users. Read, write, analyze, automate, share, and visualize — by just asking. BYOT (bring your own token) · server-side memory · audit log · real-time webhooks · MCP-ready.
Built on top of the official @smartsheet REST API v2.0 — community project, not affiliated with Smartsheet Inc.
Quick Start · What's New · Chrome extension · The Power · Cross-sheet workflow · Prompts library · Bug reports · What You Can Say · 73 Tools · Agent Reliability · MCP Server · Tests · Deploy
You know the drill. Open Smartsheet. Navigate. Click. Scroll. Find the right column. Write a formula. Test it. Fix it. Click again. Repeat for every single sheet.
What if you could just... say it?
| Before | After |
|---|---|
| Open sheet, scroll to row 47, find the column, manually edit the cell | "Update row 47, set Status to Done" |
| Google the SUMIFS syntax, write it, debug the brackets | "What's the total revenue for Q1?" |
| Navigate to sharing settings, add email, choose permission | "Share this sheet with Marie as Editor" |
| Open 3 sheets, cross-reference manually, copy-paste values | "Pull the budget total from the Finance sheet into this one" |
| Export data, open another tool, build a chart | "Generate a visual of my project timeline" |
Three shipped improvements that move the agent from "impressive demo" to "boring, dependable workflow tool".
Also shipped this month
- Prompts sidebar in the chat margin (Ctrl+Shift+K) — the full prompts library is now docked to the right of the chat, one click from the input. Hover any prompt to reveal a Copy button; click the title to insert the prompt directly into the chat. The sidebar keeps state across reloads, leaves a vertical re-open rail when collapsed, and slides into a full-width drawer on mobile.
- Header logout button with a discreet red-hover SVG icon (Ctrl+Shift+Q) — clears the session token and reopens the connect screen in one click.
- Direct Help button in the header — opens the full-screen
/helppage in a new tab so you can keep the chat alive while scanning the catalogue. - Restyled global scrollbars — slim, gradient (violet → cyan), Firefox-aware. Same look across the chat, the modals, and the dedicated
/helppage. - Chrome extension (
extension/) — MV3 side panel beside Smartsheet, sheet ID from the URL, embed modessc_ext=1for a focused UI (Chrome extension).
Want the recipes ? Open the in-app modal (Ctrl+Shift+L) or visit the dedicated
/helppage once the server is running.
The web app isn’t browser-only fiction: extension/ is a Manifest V3 Chrome extension that loads your running Controller (same FastAPI backend) inside Chrome’s side panel, docked next to Smartsheet. The content script detects the sheet ID from app.smartsheet.com URLs and passes ssc_ext=1 so the embedded iframe can strip extra chrome; the toolbar icon can show when a sheet page is detected.
| Install | Load unpacked from chrome://extensions (Developer mode) → choose the extension/ folder (step-by-step). |
| Hotkey | Ctrl+Shift+Y — open side panel (see extension README). |
| Privacy / Store | Web Store checklist and packaging live under extension/store/. |
| Trademark | Google Chrome and the Chrome logo are trademarks of Google LLC. |
The landing page frontend/index.html also highlights the extension using assets under frontend/assets/branding/.
Ask a question in plain English (or French). The AI reads your sheet, understands the structure, and answers with formatted tables, rich Markdown, and inline images. Not raw JSON. Not a wall of text. Clean, structured, actionable answers.
Every Smartsheet operation — from reading a cell to creating a webhook — is a tool the agent can call autonomously. You describe what you want. The agent decides which API calls to make, chains them together, and delivers the result. You never touch the API.
The agent doesn't just know Smartsheet formulas — it thinks in them. SUMIFS, INDEX(COLLECT(...)), cross-sheet {{references}}, hierarchy functions like CHILDREN() and ANCESTORS(). Ask for a formula and get the exact Smartsheet syntax, not a generic spreadsheet guess.
Click the mic. Speak. The agent transcribes, understands, and executes. Voice-first, keyboard-optional. Your Smartsheet has ears now.
Responses flow token by token, just like ChatGPT. You see the agent thinking in real time. No waiting for a wall of text — words appear as they're generated. The app breathes.
After every response, the agent suggests 2-4 follow-up actions as clickable chips. You never face a blank input. The AI guides you forward.
Jump between sheets instantly from a dropdown in the header. No disconnecting, no re-configuring. Multi-sheet workflows in one session.
Every conversation is saved locally and accessible from a sidebar. Reload past analyses, formulas, and decisions anytime. Your Smartsheet memory never fades.
Every tool call the agent makes is visible in real-time. You see the API call, the parameters, the response. Full transparency. Full control. No black box.
Need a diagram? A chart concept? A visual representation of your data architecture? The agent generates DALL-E 3 images and live Chart.js bar/line/pie/doughnut/scatter charts directly in the conversation. Your chat is a canvas.
Cross-sheet formulas in Smartsheet are powerful but unforgiving — you must first declare a named cross-sheet reference, then quote it inside the formula like {{my_ref}}. The agent now ships a mandatory 4-step workflow baked into its system prompt:
- Identify the source sheet (the agent now sees the numeric
idof every sheet in your account, no extralist_sheetscall needed). - Read the source schema to confirm the exact column titles and types.
- Create the cross-sheet reference with
create_cross_sheet_ref— the tool description spells out the parameters and the failure modes. - Write the formula using the canonical patterns:
VLOOKUP,INDEX/MATCH,INDEX(COLLECT(...)),MAX(COLLECT(...)),JOIN(COLLECT(...)), conditionalSUMIFS/COUNTIFS.
Backed by:
- 25+ French & English intent triggers (
ramène,récupère,lookup,vlookup,pull from,fetch,another sheet,crosssheet,named range…) so the right tools are always exposed to the LLM when you ask for a cross-sheet pull. - 38 live functional tests in
tests/functional/run_cross_sheet.pythat build two real sheets, create 8 cross-sheet refs (with retry-on-404 to survive Smartsheet's eventual-consistency window), and validate every formula round-trip. - Documented Smartsheet quirks —
AVGinstead ofAVERAGE, noAVERAGEIFS/MAXIFS/MINIFS(useSUMIFS+COUNTIFSorMAX(COLLECT(...))), JOIN must be wrapped inCOLLECT(..., <>"")to drop empty cells.
The original "agent can't bring back values from another sheet" failure mode is now covered by 8 dedicated unit tests × 16 phrasings that lock in the intent-subsetting behaviour.
Onboarding new collaborators (or yourself, three months later) is the slowest part of any agent product. The Help section solves it:
- In-chat sidebar — a dense, always-on-screen panel docked to the right of the chat (toggle with Ctrl+Shift+K or the panel button in the header). One click on a prompt inserts it into the input ready to review and send; hover reveals a one-tap Copy button. Search, accordion categories, and the open/closed state are persisted in
localStorage. Collapsing the sidebar leaves a vertical "Prompts" rail on the right edge so it's never more than one click away. - In-app modal — Ctrl+Shift+L or the book button in the header. Live search, collapsible categories, per-prompt difficulty (easy / medium / advanced) and risk (safe / caution / destructive) badges, Copy and Insert into chat actions. Use the modal when you want full prompt bodies and badges visible at a glance.
- Dedicated full-screen page —
/help. Reachable in one click from the header, sticky sidebar with category navigation and scroll-spy, ideal for screen-sharing or projecting during onboarding sessions. - 81 curated prompts across 13 categories — covering virtually every Smartsheet surface the agent can touch:
- Exploration · Rows · Columns · Cross-sheet workflows · Formulas · Sharing & permissions · Automations · Maintenance & cleanup
- Hierarchy & subtasks · Discussions & comments · Attachments & proofs · Reports & dashboards · Workspaces & navigation
- Hot-editable JSON — the catalogue lives in
frontend/data/prompts.jsonand is re-read on every API call, so you can edit it in production without restartinguvicorn. Override the path withSMARTSHEET_PROMPTS_PATHfor deployments where you want to ship the catalogue outside the repo. - Safety guardrail — every prompt flagged
risk: destructiveis automatically validated to contain a confirmation cue (confirm,preview,wait). The unit tests fail otherwise. The agent never silently executes a destructive action.
Five LLM providers, one chat. Switch model mid-conversation — the context follows you. The agent keeps the same tool repertoire and the same Smartsheet expert prompt no matter which brain you plug in.
| Provider | What it's great at | Models exposed |
|---|---|---|
| OpenAI | The reference. Best tool-calling reliability + DALL-E 3 image gen. | gpt-4o, gpt-4o-mini, gpt-4-turbo |
| Anthropic | Long-form analysis, careful reasoning, dense tables. | claude-3-5-sonnet, claude-3-5-haiku, claude-3-opus |
| OpenRouter | One API key, hundreds of models. Try Llama, Gemini, DeepSeek without managing extra keys. | Any OpenRouter slug |
| Mistral | EU-hosted, fast, cost-efficient. | mistral-large-latest, mistral-small-latest |
| Groq | Sub-second token speed thanks to LPU inference. | llama-3.3-70b-versatile, mixtral-8x7b |
The header dropdown lists only the providers whose key is detected in .env — set just the ones you actually have.
A live token-usage counter is displayed under the chat for the active provider, and a per-session rate-limiter (60 LLM calls / min, 120 WebSocket frames / min) protects you from runaway loops.
The server isn't a stateless proxy. It owns a small SQLite store so your account remembers things across sessions and tabs:
- Conversations — auto-saved on every message, reloadable from the sidebar (with optional auto-generated titles).
- Favorites — pin sheets to the header for one-click switching.
- Audit log — every tool call (especially destructive ones) is timestamped and stored.
GET /api/auditandGET /api/exportgive you the full history (RGPD-friendly). - Webhook events — incoming Smartsheet webhooks land in
POST /api/smartsheet-webhook, are persisted, and fan out in real time to every active WebSocket session that pinned the affected sheet. - Watch mode — the UI can subscribe to a sheet and surface new rows / changes the moment they happen.
Combined with the per-session token bucket and Smartsheet 429 retry-with-backoff, the backend stays steady under fast-fire conversations.
pip install -r requirements.txtcp .env.example .envSMARTSHEET_TOKEN=your_smartsheet_api_token
SHEET_ID=your_default_sheet_id
OPENAI_API_KEY=sk-...Smartsheet API token — app.smartsheet.com > Personal Settings > API Access
Sheet ID — Open any sheet, check the URL or go to File > Properties
uvicorn backend.app:app --reload --port 8100Open http://localhost:8100 — hit Quick Connect — start talking.
That's it. Three commands. You're in.
Every one of these is a real command the agent executes.
"Show me the full structure of my sheet as a table"
"Read rows 1 to 50 and give me a summary"
"What are the 10 largest amounts in the Budget column?"
"Are there any errors or inconsistencies in my data?"
"Write a SUMIFS formula to total sales by region"
"Create a cross-sheet formula that pulls the status from the Projects sheet"
"What's the correct syntax for INDEX(COLLECT(...)) with two criteria?"
"Add a PICKLIST column 'Priority' with options High, Medium, Low"
"Move completed rows to the Archive sheet"
"Who has access to this sheet? Show me the permissions"
"Generate an image that illustrates my sheet architecture"
"Create a visual for the project tracking dashboard"
73 tools organized in 16 categories. The agent picks the right ones automatically.
Account & Navigation — 10 tools
| Tool | What it does |
|---|---|
get_current_user |
Your Smartsheet profile |
list_sheets |
Every sheet you have access to |
search |
Search across your entire account |
search_sheet |
Search within a specific sheet |
list_workspaces |
All your workspaces |
get_workspace |
Workspace contents and structure |
list_folders |
Home-level folders |
get_folder |
Folder contents |
create_folder |
Create a new folder |
get_recent_items |
Recent items and favorites |
Sheet Reading & Analysis — 7 tools
| Tool | What it does |
|---|---|
get_sheet_summary |
Sheet structure: columns, types, row count |
read_rows |
Read rows with optional range |
get_row |
Get a single row by ID |
get_cell_history |
Full audit trail for any cell |
get_summary_fields |
Sheet-level KPI fields |
analyze_sheet |
Deep analysis: structure, patterns, formulas |
detect_issues |
Find errors, empty columns, inconsistencies |
Data Manipulation — 6 tools
| Tool | What it does |
|---|---|
add_rows |
Add new rows with values |
update_rows |
Update cells (values or formulas) |
delete_rows |
Delete rows by ID |
move_rows |
Move rows to another sheet |
copy_rows |
Copy rows to another sheet |
sort_sheet |
Sort by one or more columns |
Column Management — 3 tools
| Tool | What it does |
|---|---|
add_column |
Add column (TEXT_NUMBER, DATE, PICKLIST, CHECKBOX, CONTACT_LIST, DURATION, PREDECESSOR) |
update_column |
Update column title or description |
delete_column |
Delete a column |
Sheet Management — 5 tools
| Tool | What it does |
|---|---|
create_sheet |
Create a new sheet with columns |
delete_sheet |
Delete a sheet |
rename_sheet |
Rename a sheet |
copy_sheet |
Copy a sheet |
move_sheet |
Move sheet to folder/workspace |
Cross-sheet References — 2 tools
| Tool | What it does |
|---|---|
list_cross_sheet_refs |
List all cross-sheet references |
create_cross_sheet_ref |
Create a ref for INDEX(COLLECT(...)) formulas |
Sharing & Collaboration — 4 tools
| Tool | What it does |
|---|---|
list_shares |
List sharing permissions |
share_sheet |
Share with a user (VIEWER / EDITOR / ADMIN) |
update_share |
Update permission level |
delete_share |
Remove access |
Discussions & Comments — 4 tools
| Tool | What it does |
|---|---|
list_discussions |
Sheet-level discussions |
list_row_discussions |
Discussions on a specific row |
add_comment |
Reply to a discussion |
create_row_discussion |
Start a new discussion on a row |
Attachments — 6 tools
| Tool | What it does |
|---|---|
list_attachments |
List all attachments on a sheet |
list_row_attachments |
List attachments on a single row |
get_attachment |
Get details and download URL |
attach_url_to_sheet |
Attach a URL/link (Drive, Dropbox, OneDrive, web) to a sheet |
attach_url_to_row |
Attach a URL/link to a specific row |
delete_attachment |
Delete an attachment by ID |
Forms — 1 tool
| Tool | What it does |
|---|---|
list_sheet_forms |
List forms on a sheet (falls back to permalink — Smartsheet API exposure is limited) |
Update Requests — 3 tools
| Tool | What it does |
|---|---|
list_update_requests |
List pending update requests |
create_update_request |
Send an update request to one or more emails for specific row(s) |
delete_update_request |
Cancel an outstanding update request |
Proofs (Premium) — 2 tools
| Tool | What it does |
|---|---|
list_row_proofs |
List proofs on a row (Premium feature) |
create_row_proof_from_url |
Create a proof from a URL on a row |
Reports & Dashboards — 4 tools
| Tool | What it does |
|---|---|
list_reports |
List all reports |
get_report |
Get report data |
list_dashboards |
List all dashboards |
get_dashboard |
Dashboard details and widgets |
Templates & Webhooks — 5 tools
| Tool | What it does |
|---|---|
list_templates |
Public templates |
list_webhooks |
Configured webhooks |
create_webhook |
Create a webhook |
update_webhook |
Enable/disable or reconfigure a webhook in place |
delete_webhook |
Delete a webhook |
Automations — 4 tools
| Tool | What it does |
|---|---|
list_automations |
List automation rules |
get_automation |
Get details of one rule |
update_automation |
Enable/disable or rename a rule (CREATE not supported by Smartsheet API) |
delete_automation |
Delete an automation rule |
Workspace Sharing — 4 tools
| Tool | What it does |
|---|---|
list_workspace_shares |
List sharing on a workspace |
share_workspace |
Share workspace with a user (cascades to all sheets inside) |
update_workspace_share |
Change a user's access level on a workspace |
delete_workspace_share |
Remove a user from a workspace |
Cell Linking & AI — 3 tools
| Tool | What it does |
|---|---|
create_cell_link |
One-way live link from a source cell to a target cell (distinct from cross-sheet refs) |
generate_image |
Generate visuals with DALL-E 3 (inline in chat) |
generate_chart |
Render a Chart.js bar/line/pie/etc. inline in chat |
| Category | Count | Functions |
|---|---|---|
| Numeric | 25 | ABS AVG AVGW CEILING CHAR COUNT FLOOR INT LARGE LEN MAX MEDIAN MIN MOD MROUND RANKAVG RANKEQ ROUND ROUNDDOWN ROUNDUP SMALL SUM UNICHAR DECTOHEX HEXTODEC |
| Logic | 16 | AND CONTAINS HAS IF IFERROR ISBLANK ISBOOLEAN ISCRITICAL ISDATE ISERROR ISEVEN ISNUMBER ISODD ISTEXT NOT OR |
| Text | 10 | FIND JOIN LEFT LOWER MID REPLACE RIGHT SUBSTITUTE UPPER VALUE |
| Date | 14 | DATE DATEONLY DAY MONTH NETDAYS NETWORKDAY NETWORKDAYS TIME TODAY WEEKDAY WEEKNUMBER WORKDAY YEAR YEARDAY |
| Advanced / Lookup | 18 | AVERAGEIF COLLECT COUNTIF COUNTIFS COUNTM DISTINCT INDEX MATCH NPV PERCENTILE PRORATE STDEVA STDEVP STDEVPA STDEVS SUMIF SUMIFS VLOOKUP |
| Hierarchy | 6 | ANCESTORS CHILDREN DESCENDANTS PARENT SUCCESSORS TOTALFLOAT |
Beyond functions — the agent masters:
[Column]@rowreferences,[Column]:[Column]ranges, absolute row refs- Cross-sheet
{{reference}}patterns:INDEX(COLLECT({{Values}}, {{Keys}}, [Key]@row), 1) - Column formulas, Summary fields, Conditional formatting rules
- Reports (multi-sheet aggregation), Dashboards (live KPI widgets)
- Gantt, Calendar, Card/Kanban views
- Automations, Forms, Proofing, Cell history, Row locking
A complete Model Context Protocol server — plug Smartsheet into Claude Desktop, Cursor, or any MCP client. 52 of the 73 tools are exposed through MCP (the chat-only ones — generate_image, generate_chart, etc. — stay in the web app).
# stdio (Claude Desktop / Cursor)
SMARTSHEET_TOKEN=your_token python -m backend
# HTTP
SMARTSHEET_TOKEN=your_token python -m backend --transport http{
"mcpServers": {
"smartsheet": {
"command": "python",
"args": ["-m", "backend"],
"cwd": "/path/to/smartsheet-controller",
"env": { "SMARTSHEET_TOKEN": "your_token_here" }
}
}
}{
"mcpServers": {
"smartsheet": {
"command": "python",
"args": ["-m", "backend"],
"cwd": "/path/to/smartsheet-controller",
"env": { "SMARTSHEET_TOKEN": "your_token_here" }
}
}
}| MCP Resources | |
|---|---|
smartsheet://sheets |
All sheets in the account |
smartsheet://user |
Authenticated user profile |
smartsheet://sheets/{id}/summary |
Sheet structure |
| MCP Prompts | |
|---|---|
| Analyze Sheet | Deep structure analysis with suggestions |
| Sheet Audit | Security, sharing & quality audit |
| Explore Account | Full account exploration |
smartsheet-controller/
├── backend/
│ ├── app.py → FastAPI + WebSocket + session mgmt + 20+ HTTP routes
│ ├── agent.py → AI agent loop, expert prompt, tool dispatch, suggestions
│ ├── tools.py → 73 tool definitions + dispatcher + DALL-E + Chart.js
│ ├── llm_router.py → 5-provider streaming router (OpenAI / Anthropic / OpenRouter / Mistral / Groq)
│ ├── smartsheet_client.py → Smartsheet REST wrapper (httpx, retry, in-memory cache)
│ ├── db.py → SQLite persistence (users, sessions, conversations, favorites, audit, webhooks)
│ ├── rate_limit.py → Token-bucket rate limiter (per-session LLM + WS)
│ ├── logging_config.py → Structured JSON logs with token redaction
│ ├── mcp_server.py → MCP server (52 tools, 3 resources, 3 prompts)
│ └── __main__.py → MCP CLI entry point (stdio / http)
├── frontend/
│ ├── index.html → Glassmorphism SPA (voice, streaming, history, suggestions, watch)
│ └── assets/ → Brand & marketing assets (PNG, SVG)
├── tests/
│ ├── unit/ → 196 fast tests (no I/O, mocked transports)
│ │ ├── test_rate_limit.py rate limiter
│ │ ├── test_llm_router.py provider parsing + usage tracking
│ │ ├── test_tools.py intent routing + chart generation
│ │ ├── test_tools_dispatch.py contract: every tool dispatchable
│ │ ├── test_smartsheet_client.py mock-transport CRUD + retry + cache
│ │ ├── test_db.py SQLite layer (every CRUD path)
│ │ ├── test_agent.py agent helpers
│ │ ├── test_agent_loop.py run() loop: tools, confirm, MAX_ROUNDS
│ │ ├── test_app_helpers.py _friendly_error, provider detection
│ │ └── test_mcp_smoke.py MCP server boot + tool registry
│ ├── integration/ → 41 tests against the real Smartsheet API
│ │ ├── test_smartsheet_live.py read + create→modify→delete lifecycle
│ │ └── test_app_endpoints*.py every FastAPI HTTP route
│ ├── e2e/ → 9 full-stack WebSocket flow tests
│ │ ├── test_websocket_flow.py handshake + agent + suggestions
│ │ └── test_websocket_advanced.py cancel, confirm/reject, rate-limit, multi-turn
│ └── README.md → How to run, what each layer covers
├── data/ → SQLite DB (created at first launch, git-ignored)
├── Dockerfile → Slim Python 3.11 image, non-root, /health probe
├── docker-compose.yml → One-command stack with bind-mount for data/
├── pytest.ini → markers: unit / integration / e2e / live_llm
├── ROADMAP.md → 7+ sprints, journal, deliverables
├── requirements.txt → Runtime deps
├── requirements-dev.txt → + pytest, pytest-asyncio, pytest-cov
├── .env → API keys (LLM providers + optional SMARTSHEET_TOKEN)
└── README.md
You (voice or text)
│
▼
WebSocket ──→ FastAPI ──→ Agent ──→ LLM Router
│ ┌──────────┐
├─────▶│ OpenAI │
├─────▶│ Anthropic│
├─────▶│OpenRouter│
├─────▶│ Mistral │
└─────▶│ Groq │
│ └──────────┘
73 tool calls
│
▼
Smartsheet API v2.0 ◀──── webhooks ──┐
│ │
▼ │
Streaming · Tables · Images · Charts │
│ │
▼ │
Chat UI ◀────── live fan-out ──────┘
│
▼
SQLite (conversations · favorites · audit)
LLMs hallucinate. They confuse rows with columns, retry the same failing call 12× in a row, invent column names that don't exist, and write formulas with functions Smartsheet doesn't support. The agent loop is wrapped in a safety harness that intercepts these failure modes before they hit the API, gives the model a structured error with a recovery path, and surfaces the rescue to the user as a discrete banner — so you can see exactly when the harness saved the day.
| # | Safety net | What it catches | Where it lives |
|---|---|---|---|
| 1 | Loop killer | Same (tool, args) called > 3× in one turn — typically because the model can't read its own error |
backend/agent.py · LOOP_REPEAT_THRESHOLD |
| 2 | Schema-guard | add_rows / update_rows payload references columns that don't exist on the sheet (the original "row created with no values" bug) |
backend/tools.py · _validate_columns_for_write |
| 3 | Verb subsetting | The intent classifier missed the user's verb ("ajoute", "rajoute", "create", …) so it didn't expose the right write tool | backend/tools.py · _WRITE_VERB_TOKENS |
| 4 | JSON parse recovery | Model emitted malformed tool arguments — we feed back a structured INVALID_JSON error instead of crashing |
backend/agent.py · __parse_error__ branch |
| 5 | Verified formula catalog | Empirically tested list of supported Smartsheet functions + workarounds for the unsupported ones (POWER → ^, IFS → nested IF, …) |
backend/agent.py · SYSTEM_PROMPT |
| 6 | Action discipline | Seven hard rules in the system prompt that ban known anti-patterns (read spam, fabricated IDs, repeating after error, …) | backend/agent.py · ## ACTION DISCIPLINE |
| 7 | Contextual recovery hints | Translates raw HTTP errors (401, 403, 404, 409, 429, 500) into actionable Smartsheet-specific guidance | backend/tools.py · _friendly_http_error |
| 8 | Few-shot examples + decision table | Six canonical interaction patterns + a 12-row intent→tool cheatsheet so the model has worked examples | backend/agent.py · ## EXAMPLES and ## INTENT → TOOL CHEATSHEET |
Every activation increments a counter exposed at GET /api/usage (Settings panel):
{
"agent_metrics": {
"turns": 42, // chat turns processed
"tool_calls": 117, // tool calls actually executed
"tool_errors": 4, // tool calls that returned an error payload
"loop_blocked": 1, // times the loop killer fired
"schema_guard_triggered": 2, // times add_rows/update_rows was blocked
"parse_errors": 0, // malformed tool-args from the model
"user_rejections": 1, // destructive actions you rejected
"rounds_exhausted": 0 // turns that hit MAX_TOOL_ROUNDS
}
}When a safety net activates, the agent also pushes an agent_hint event to the WebSocket so the UI can render a colored banner (info / warn /
error) telling the user why the agent is correcting itself rather than burying the explanation inside a tool result.
The harness is covered by 40+ dedicated tests:
# All P3 reliability tests in one shot
pytest tests/unit/test_agent_loop_killer.py \
tests/unit/test_tools_schema_guard.py \
tests/unit/test_tools_intent_subsetting.py \
tests/unit/test_agent_metrics.py \
tests/unit/test_agent_hints.py \
tests/e2e/test_agent_safety_net.pyEach scenario reproduces a real failure mode (the original "agent calls add_rows when asked to add a column" bug, dead-end retry loops, etc.) and asserts that the harness intercepts the mistake and the follow-up correction succeeds.
Once the app is in your team's hands, the fastest way to keep improving it is to make reporting bugs friction-free. Every page now has a 🐛 Report a bug button in the header (also available via Ctrl+Shift+B). Clicking it opens a modal that:
- Lets the reporter pick a severity (Low / Normal / High / Blocker).
- Asks for a free-text description of what happened (required) and optional reproduction steps.
- Auto-attaches a JSON context bundle: current
session_id,sheet_id, conversation id, last 6 messages, agent metrics snapshot, browser user-agent, viewport size, WebSocket state, LLM provider/model — so you don't have to play 20 questions to reproduce. - Posts to
POST /api/bug-reports(no auth required: a bug can occur at the login screen).
Reports are stored two ways for safety:
- SQLite — table
bug_reports(status:open/triaged/fixed/wontfix). - JSONL append-only mirror —
data/bug_reports.jsonl, configurable viaBUG_REPORTS_JSONL_PATH. Survives DB resets.
To read or triage reports, set an admin token in your .env:
BUG_REPORTS_ADMIN_TOKEN=pick-a-long-random-secretWithout this env var the admin endpoints are disabled (always 403). No risk of leaking reports if you forget.
# List the 50 most recent open bugs
curl -H "X-Admin-Token: $BUG_REPORTS_ADMIN_TOKEN" \
"http://localhost:8100/api/bug-reports?status=open&limit=50"
# Mark report #12 as fixed
curl -X POST -H "X-Admin-Token: $BUG_REPORTS_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"fixed"}' \
http://localhost:8100/api/bug-reports/12/statusThe feature is covered by 25 dedicated tests in tests/unit/test_bug_reports.py (DB CRUD, validation, server-side context enrichment, admin
gate, JSONL mirror, status transitions).
pip install -r requirements-dev.txt
pytest # everything (445 tests, ~4 min)
pytest tests/unit # 389 fast tests, <2s, no network
pytest tests/integration # 41 real Smartsheet API tests
pytest tests/e2e # 15 WebSocket end-to-end (stubbed LLM, no cost)
pytest --cov=backend --cov-report=html # coverage report → htmlcov/| Layer | Count | Speed | Network | Notable coverage |
|---|---|---|---|---|
unit |
401 | < 3 s | none | Rate limiter · LLM router · _friendly_error (no secrets leaked) · dispatch contract for all 73 tools · agent.run() loop (confirm, parse-error recovery, MAX_TOOL_ROUNDS, image/chart events) · SQLite CRUD · MCP smoke (52 tools registered) · Loop killer + Schema-guard + Intent subsetting (107 P3 reliability tests) · Cross-sheet intent subsetting (16 phrasings) · Bug reports CRUD + JSONL mirror (25 tests) · Prompts library JSON contract + endpoint + sidebar wiring (25 tests, incl. dynamic per-category validation + destructive-prompt confirmation guardrail across all 81 prompts + 12 sidebar contract tests pinning down the in-chat panel) · _build_welcome resilience (12 tests covering None row/column counts and threshold boundaries) |
integration |
41 | medium | Smartsheet | Real read calls + create→modify→delete lifecycle on a throwaway sheet · all FastAPI routes including /api/csv-to-sheet, /api/smartsheet-webhook (challenge + payload fan-out), favorites, conversations CRUD, audit, RGPD export, model switching |
e2e |
15 | medium | Smartsheet | Full FastAPI lifespan · WebSocket handshake · agent loop with stubbed LLM · suggestions extraction · mid-stream cancel · destructive-tool confirm/reject · WS rate-limit response · multi-turn in one connection · scripted-LLM safety-net scenarios (6 tests verifying the harness intercepts column/row confusion, infinite loops, schema violations) |
functional (live) |
38 | slow | Smartsheet | Cross-sheet formulas: aggregation (SUM/AVG/COUNT), conditional aggregation (SUMIFS, COUNTIFS, MAX/MIN(COLLECT)), lookups (VLOOKUP, INDEX/MATCH, INDEX/COLLECT), date ops, JOIN — with 404-retry logic to survive Smartsheet's eventual-consistency window |
Integration & e2e tests skip cleanly when
SMARTSHEET_TOKEN/SHEET_IDaren't in.env. Tests never touch the production SQLite DB — each test gets its own temp file. LLM calls are stubbed by default; setENABLE_LIVE_LLM=1to opt into real provider calls.
cp .env.example .env # fill in at least OPENAI_API_KEY (SMARTSHEET_TOKEN is optional - users now BYOT)
docker compose up -d --build
# → http://localhost:8100 (health: http://localhost:8100/health)The container runs uvicorn backend.app:app as a non-root user on port 8100, exposes a /health endpoint for orchestrator probes, and reads secrets from .env.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn backend.app:app --host 0.0.0.0 --port 8100 --proxy-headers --forwarded-allow-ips '*'Example /etc/systemd/system/smartsheet-controller.service:
[Unit]
Description=Smartsheet Controller
After=network.target
[Service]
Type=simple
User=smartsheet
WorkingDirectory=/opt/smartsheet-controller
EnvironmentFile=/opt/smartsheet-controller/.env
ExecStart=/opt/smartsheet-controller/.venv/bin/uvicorn backend.app:app \
--host 127.0.0.1 --port 8100 --proxy-headers --forwarded-allow-ips '*'
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now smartsheet-controller
sudo journalctl -u smartsheet-controller -fserver {
listen 443 ssl http2;
server_name chat.example.com;
ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
client_max_body_size 2m;
location / {
proxy_pass http://127.0.0.1:8100;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket upgrade (used by /ws/{session_id})
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location = /health { proxy_pass http://127.0.0.1:8100/health; access_log off; }
}
server {
listen 80;
server_name chat.example.com;
return 301 https://$host$request_uri;
}- BYOT flow — end users paste their own Smartsheet token in the UI. The server stores it only in-memory for the session; sessions auto-expire after
SESSION_IDLE_TIMEOUTseconds (default1800). - Secrets — only LLM provider keys need to live in
.envon the server (OPENAI_API_KEY,ANTHROPIC_API_KEY, ...). TheSMARTSHEET_TOKENin.envis only used for the "Quick Connect (dev)" button. - Rate limiting — per-session token buckets (LLM 60/min, WS 120/min) are applied automatically; Smartsheet 429s are retried with exponential backoff.
- Logging — stdout is redacted of API tokens; set
LOG_LEVEL=DEBUGto see more. - Health probe —
GET /healthreturns{"status": "ok", "sessions": N, "uptime": ...}.
| Smartsheet — official GitHub org | @smartsheet (verified, SDKs Python · JS · Java · C#) |
| Smartsheet Platform — DevRel GitHub | @smartsheet-platform (incl. their own MCP server) |
| Smartsheet API Docs | smartsheet.redoc.ly |
| Smartsheet Developer Portal | developers.smartsheet.com |
| Smartsheet Functions | help.smartsheet.com/functions |
| Smartsheet Learning Center | help.smartsheet.com |
| Smartsheet Community | community.smartsheet.com |
| Model Context Protocol | modelcontextprotocol.io |
| OpenAI Platform | platform.openai.com |
| Anthropic Docs | docs.anthropic.com |
73 tools · 80 formulas · 7 LLMs · 445 green tests · 81 in-app prompts · Full voice · Streaming · History · Audit · MCP · Bug reports · One-click logout.
Your data listens. Your sheets respond. Your workflow accelerates.
Built with FastAPI · Smartsheet API · OpenAI · Anthropic · OpenRouter · Mistral · Groq · MCP
Smartsheet® and the Smartsheet logo are trademarks of Smartsheet Inc. (@smartsheet). This project is an independent community tool and is not endorsed by or affiliated with Smartsheet Inc.






