Skip to content

Repository files navigation

streamlit-coco

Built by Devoteam Snowflake Partner. Ask CoCo for Streamlit: a floating chat window, in front of a Cortex Code agent, that you add to your own application with one call. You say what the agent may do alone and what it must ask first. The window shows each request in plain words, with the exact command, file text or SQL statement one click away.

CI License

0.2.1 (alpha). The API may still move. The default interface is still the 0.1.9 chat family (chat, panel, copilot_rail). The floating window is opt-in: mini=True on those calls, or ask_coco(cwd). See doc/features/ask-coco/ask-coco.md.

Repo: github.com/lletourmy/streamlit-coco (temporary PyPI source) · Dev: streamlit-coco-dev SDK docs: Cortex Code Agent SDK


What you get

  • A launcher at the bottom right of the page, and a window that opens from it. The window is the whole interface: connect, ask, watch the steps, answer requests.
  • Rights written as code, in one object (a Policy). The default is the safest: reading inside the working folder is free, everything else asks, each time.
  • Every request names what it is about and shows the exact text of what would run.
  • Buttons of your own page can ask CoCo something in the user's name, and read the answer back.
  • A scripted agent (STREAMLIT_COCO_AGENT=mock) so you can build and test with no Snowflake login.
  • Headless query() and CocoSession for scripts and CI.

When not to use

  • You want CoCo Desktop, the CLI, or an IDE extension. This is an embed in your Streamlit application. It has no file tree and no editor.
  • The Streamlit host cannot run CoCo. The agent runs on the server (the Cortex Code program on the host). Streamlit Community Cloud without that setup will not work.
  • You are not building in Python and Streamlit. There is no TypeScript package. Use the Cortex Code Agent SDK directly.
  • You would not type the SQL yourself on this role. The agent uses the Snowflake role of the connection. Do not wire ACCOUNTADMIN into a web page.

Install

uv add "streamlit-coco[sdk]"
# or: pip install "streamlit-coco[sdk]"

From a clone (editable, with the tests): make install.

Needs:

  1. Python 3.10 or later, Streamlit 1.55 or later.
  2. For a real agent: the Cortex Code program on the PATH (cortex --version), a Snowflake connection (~/.snowflake/connections.toml or config.toml), and cortex-code-agent-sdk (the sdk extra). The window says what is missing and how to fix it.
  3. For a scripted agent: nothing more. See below.

Full local setup: doc/deployment/local.md. Every public name: doc/api.md.


First application: show the window

Save this as app.py.

import streamlit as st

import streamlit_coco as st_coco

st.set_page_config(page_title="My app")
st.title("My app")
st.write("Open Ask CoCo with the launcher at the bottom right of the page.")

# One call: the launcher and the window. CoCo works in this folder.
st_coco.ask_coco(".")

Run it without a Snowflake login, on the scripted agent:

STREAMLIT_COCO_AGENT=mock streamlit run app.py        # PowerShell: $env:STREAMLIT_COCO_AGENT="mock"

What happens: click Ask CoCo (bottom right), then Connect mock. Type a message and press Enter. The scripted agent answers: "This is a scripted answer. You wrote: ..." This was run in Edge on Streamlit 1.65.0.

Without the variable, the same file talks to a real Cortex Code agent. The window offers your Snowflake connections and connects when the user presses Connect. Nothing starts before that.

ask_coco(cwd) is the only call a first application needs. The argument is the agent's working folder. A relative path (like ".") is taken from the folder the Streamlit server was started in, not from the folder of the script. The package checks that the working folder exists and is a directory before connecting, and raises CocoError otherwise. It does not create the folder: create it first. It returns a handle; see Talk to the window.


Second application: your own rights

The agent may list one command, change Markdown files, and run SQL. Each of those still asks first, unless it is a read. Save this as app_rights.py.

import streamlit as st

import streamlit_coco as st_coco
from streamlit_coco import policy

st.set_page_config(page_title="My app with rights")
st.title("My app with rights")
st.write("Reading in this folder is free. Everything else asks first.")

# One command, written exactly as the agent must type it. It is shown as a read.
status = policy.Command(
    name="Look at what has changed",
    argv=("git", "status", "--short"),
    reads="The list of changed files",
)

rights = policy.FolderPolicy(
    ".",
    commands=[status],  # this exact line is a read, under its plain name
    changes="*.md",  # changing a Markdown file is ordinary; any other file is flagged
    sql=True,  # a SQL call shows its exact statement
)

st_coco.ask_coco(".", policy=rights)

Run it the same way, from a folder that holds a README.md:

STREAMLIT_COCO_AGENT=mock streamlit run app_rights.py

Connect, then type these words. The scripted agent picks its script from a keyword:

Type What the window shows
status A request to look at what has changed: Allow CoCo to look at what has changed? It is a read. Show command opens the exact line, git status --short.
edit A request to change a file: Allow CoCo to change a file? It says README.md, one line. Show change opens the text.
snowflake drop A request to run a statement that can change Snowflake. It says DROP. Show statement opens drop table DEMO.PUBLIC.OLD_CALLS.

If you type while a request waits, the message is queued behind it ("Queued", with Take back) and is sent after Allow or Decline.

To see a file that changes does not call ordinary, give the policy a narrower rule, for example changes="docs/*.md". The edit request on README.md is then flagged: it reads "Allow CoCo to change a file outside what it usually changes? It cannot be undone.", it names the full path of the file instead of README.md, and its note says "1 line, outside what it usually changes". Show change still opens the text. With changes="*.md" it is the ordinary request shown above.

The scripted agent only ever makes its own fixed calls, so it cannot exercise every rule you write:

  • The one command it asks for is git status --short. Another command you list in commands cannot be tried with it.
  • edit changes the first Markdown file of the folder, and write creates coco_note.md. A rule about other file types shows only as "not ordinary", on those two files.

In each case nothing runs until the user presses Allow. Decline ends the call. The scripted agent never runs the command or reaches Snowflake: it only shows and asks. With a real agent the same policy decides, and Allow runs the call.

What each argument did:

  • commands=[status]: a call whose command is exactly this line is a read, named "Look at what has changed". Any other command asks as a change, with its whole line.
  • changes="*.md": a change to a Markdown file is ordinary. A change to any other file asks too, and says it is unusual.
  • sql=True: a SQL call shows its exact statement. A statement that surely only reads is a query. Anything else is a statement that can change Snowflake.

More switches (standing, lookups, also_read, git_readonly) are in doc/api.md. A runnable version with a standing permission is examples/approval_gate.py.


Talk to the window

ask_coco returns the window's handle. st_coco.get_window() returns the same handle anywhere else on the page.

import streamlit as st

import streamlit_coco as st_coco

coco = st_coco.ask_coco(".", prompts=["List the files here"])

if st.button("Summarise this folder"):  # ask() reruns the page: keep it behind a button
    coco.ask("Summarise this folder", "Read the files here and say what they are for.", key="sum")

done = coco.result("sum")  # None until finished, then {"text", "data"}, once
if done:
    st.write(done["text"])
Call What it does
coco.ask(label, text, key=...) A button of your page asks CoCo in the user's name. The thread shows label.
coco.result(key) The finished answer of that request: {"text", "data"}, handed over once.
coco.form(title, fields, key=...) A form your application asks, answered in the window. No agent needed.
coco.notice(text) One line in the thread, in your words.
coco.open(), coco.new(), coco.disconnect() Open the window, end the conversation, end the session.
coco.latest_answer The text of the latest answer, for a page that shows it elsewhere.

Other arguments of ask_coco: features (what the window offers), context (what your page knows when a message is sent), labels (every sentence, to translate), theme (colours, font), options (model, connection, structured output), on_files_changed. All of them are in doc/api.md.


Try the demos

make simple-chat   # panel, a test-prompt picker, rights as pills
make modern-chat   # Copilot rail, floating window, or both
make approval      # a listed command, a file rule, SQL, a standing permission
make structured    # a result handed to the page as data
make cwd-upload    # files attached in chat, and an uploader on the page
make headless      # asyncio query() and a Conversation answered in code
make backlog       # Product Backlog Desk: a multipage application
make bi-semantic   # Tableau / Power BI to semantic view
make app-builder   # App Builder: type library, brief, Preview

Each Streamlit demo has an Ask CoCo control (Off / On) in the sidebar. Off keeps that page's chat, panel, or Copilot rail. On leaves Ask CoCo minimized until Minimize on that CoCo, which hides it and opens the window. Restore in the window closes it and brings the rail or chat back.

To run any of them with no login: STREAMLIT_COCO_AGENT=mock make simple-chat (PowerShell: set $env:STREAMLIT_COCO_AGENT first). make chat still launches the simple demo.

Exploratory prompts: examples/testdata/prompts.json. Backlog Desk: examples/backlog_desk/README.md · BI to Semantic: examples/bi_to_semantic/README.md · App Builder: examples/app_builder/README.md.


Architecture

The agent is on the server. The browser sees one Streamlit component.

Browser  ──►  Ask CoCo window (one component, in a fragment)
                  │  one action at a time; the page does not rerun
                  ▼
             Conversation   (thread, steps, requests, queue: plain data)
                  │  every tool call reaches the Gate
                  ▼
             Gate + Policy  (free, asks, or refused; waits for Allow / Decline)
                  ▼
             Cortex adapter ──►  Cortex Code Agent SDK ──►  Cortex Code ──►  Snowflake

The scripted agent (streamlit_coco.testing) goes through the same Conversation, Policy and Gate. Only the model and the programs are replaced.

streamlit_coco/
├── window.py        # ask_coco(), get_window(), the handle
├── conversation.py  # the contract between the window and the agent (read its docstring)
├── policy.py        # Policy, FolderPolicy, Command, git_readonly, request, inside
├── gate.py          # the decision that waits for Allow / Decline
├── features.py      # Features, Attachments
├── labels.py        # every sentence of the window, by key
├── cortex.py        # the Cortex Code adapter
├── testing/         # scripted agent, fixtures, harness
├── static/          # the window's script, styles and font
├── session.py       # CocoSession (headless)
├── query.py         # query() (headless)
├── viewer.py        # app_viewer()
└── uploader.py      # cwd_uploader()
examples/            # demos
doc/                 # API, feature files, checklists

Notes: doc/prd.md · threat model: doc/security/threat-model.md · feature files: doc/features/README.md.


Headless

import asyncio
import streamlit_coco as coco

async def run():
    async for event in coco.query("Profile ANALYTICS.CUSTOMERS"):
        if event.type == "result":
            print(event.structured_output)

asyncio.run(run())

Development

make install       # uv sync --extra dev
make check         # ruff and the unit tests (not the browser tests)
make e2e-install   # Playwright and Chromium, once
make e2e           # browser tests of the window, on the scripted agent
make test-all      # check, e2e and the dependency audit
make format        # ruff format and fix
make build         # sdist and wheel
make help          # every target

make e2e uses the installed Edge when Playwright's Chromium is not installed. No test calls a live agent. Process: doc/testing.md. Live checks before a release: doc/features/ask-coco/test-checklist.md.

Releases: develop here, sync and tag on lletourmy/streamlit-coco for PyPI (guide).

Docs: API · Ask CoCo feature file · PRD · Roadmap · Deployment · Changelog · AGENTS.md


Ownership

Role Name Contact
Asset Owner Laurent Letourmy laurent.letourmy@devoteam.com
Asset Contributor Alentz Joseph alentz.joseph@devoteam.com

Snow Builders level: N0 (alpha), targeting N1. Identity sheet: ID.md.


License

Apache-2.0