diff --git a/README.md b/README.md
index 87004ca..50ef022 100644
--- a/README.md
+++ b/README.md
@@ -301,7 +301,7 @@ Production uses `workers=1` (PTY state is process-local), `threads=16` (concurre
📁 Project Structure
```
-coding-agents-in-databricks/
+coding-agents-databricks-apps/
├── app.py # Flask backend + PTY management + setup orchestration
├── app_state.py # Shared app state (setup progress, session registry)
├── app.yaml.template # Databricks Apps deployment config template
diff --git a/app.py b/app.py
index 514c4bc..71ac4f4 100644
--- a/app.py
+++ b/app.py
@@ -43,6 +43,7 @@
SESSION_TIMEOUT_SECONDS = 86400 # No poll for 24 hours = dead session
CLEANUP_INTERVAL_SECONDS = 900 # Check for stale sessions every 15 min
GRACEFUL_SHUTDOWN_WAIT = 3 # Seconds to wait after SIGHUP before SIGKILL
+MAX_CONCURRENT_SESSIONS = int(os.environ.get("MAX_CONCURRENT_SESSIONS", "5"))
# Logging setup
logging.basicConfig(level=logging.INFO)
@@ -604,7 +605,7 @@ def read_pty_output(session_id, fd):
try:
readable, _, errors = select.select([fd], [], [fd], 0.05)
if readable or errors:
- output = os.read(fd, 4096)
+ output = os.read(fd, 65536)
if not output:
# EOF — process exited
break
@@ -956,6 +957,11 @@ def configure_pat():
@app.route("/api/session", methods=["POST"])
def create_session():
"""Create a new terminal session."""
+ # Quick reject before forking a PTY (approximate — authoritative check below)
+ with sessions_lock:
+ if len(sessions) >= MAX_CONCURRENT_SESSIONS:
+ return jsonify({"error": f"Maximum {MAX_CONCURRENT_SESSIONS} concurrent sessions reached. Close an existing session first."}), 429
+
data = request.get_json(silent=True) or {}
label = data.get("label", "")
try:
@@ -997,6 +1003,15 @@ def create_session():
session_id = str(uuid.uuid4())
with sessions_lock:
+ # Authoritative check under the same lock as insertion — prevents
+ # TOCTOU race where two concurrent requests both pass the early check.
+ if len(sessions) >= MAX_CONCURRENT_SESSIONS:
+ os.close(master_fd)
+ try:
+ os.kill(pid, signal.SIGKILL)
+ except OSError:
+ pass
+ return jsonify({"error": f"Maximum {MAX_CONCURRENT_SESSIONS} concurrent sessions reached. Close an existing session first."}), 429
sessions[session_id] = {
"master_fd": master_fd,
"pid": pid,
diff --git a/app.yaml b/app.yaml
index e6bb8cd..5596e08 100644
--- a/app.yaml
+++ b/app.yaml
@@ -12,3 +12,5 @@ env:
value: databricks-gpt-5-3-codex
- name: CLAUDE_CODE_DISABLE_AUTO_MEMORY
value: 0
+ - name: MAX_CONCURRENT_SESSIONS
+ value: "5"
diff --git a/docs/deployment.md b/docs/deployment.md
index 09959da..36267c6 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -11,7 +11,7 @@ The simplest way — no CLI, no cloning, everything stays in the Databricks UI.
1. Go to **Databricks → Apps → Create App**
2. Choose **Custom App** and connect this Git repo:
```
- https://github.com/datasciencemonkey/coding-agents-in-databricks.git
+ https://github.com/datasciencemonkey/coding-agents-databricks-apps.git
```
3. Click **Deploy**
4. Open the app — on first terminal session, paste a short-lived PAT when prompted
@@ -30,8 +30,8 @@ If you prefer working from the terminal or need more control:
```bash
databricks repos create \
- --url https://github.com/datasciencemonkey/coding-agents-in-databricks.git \
- --path /Workspace/Users//apps/coding-agents-in-databricks
+ --url https://github.com/datasciencemonkey/coding-agents-databricks-apps.git \
+ --path /Workspace/Users//apps/coding-agents-databricks-apps
```
### 2. Configure `app.yaml`
@@ -56,7 +56,7 @@ No secrets or resources to configure. On first terminal session, paste a short-l
```bash
databricks apps deploy \
- --source-code-path /Workspace/Users//apps/coding-agents-in-databricks
+ --source-code-path /Workspace/Users//apps/coding-agents-databricks-apps
```
> **Tip:** To update later, just `git pull` in the workspace repo and re-deploy.
diff --git a/pyproject.toml b/pyproject.toml
index 02cb9ca..774238c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "coda"
-version = "0.16.7"
+version = "0.17.0"
description = "CoDA - Coding Agents on Databricks Apps"
requires-python = ">=3.10"
dependencies = [
@@ -9,8 +9,7 @@ dependencies = [
"simple-websocket>=1.0",
"claude-agent-sdk",
"databricks-sdk>=0.20.0",
- "mlflow-tracing>=3.4",
- "opentelemetry-exporter-otlp-proto-grpc",
+ "mlflow-skinny==3.10.1",
"requests",
"cryptography>=46.0.6",
]
diff --git a/requirements.txt b/requirements.txt
index b7f405c..46f445a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,7 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o requirements.txt
+annotated-doc==0.0.4
+ # via fastapi
annotated-types==0.7.0
# via pydantic
anyio==4.13.0
@@ -20,7 +22,7 @@ blinker==1.9.0
# flask
# flask-socketio
cachetools==7.0.5
- # via mlflow-tracing
+ # via mlflow-skinny
certifi==2026.2.25
# via
# httpcore
@@ -36,7 +38,10 @@ click==8.3.1
# via
# flask
# flask-socketio
+ # mlflow-skinny
# uvicorn
+cloudpickle==3.1.2
+ # via mlflow-skinny
cryptography==46.0.6
# via
# coda (pyproject.toml)
@@ -45,19 +50,21 @@ cryptography==46.0.6
databricks-sdk==0.102.0
# via
# coda (pyproject.toml)
- # mlflow-tracing
+ # mlflow-skinny
+fastapi==0.135.3
+ # via mlflow-skinny
flask==3.1.3
# via
# coda (pyproject.toml)
# flask-socketio
flask-socketio==5.6.1
# via coda (pyproject.toml)
+gitdb==4.0.12
+ # via gitpython
+gitpython==3.1.46
+ # via mlflow-skinny
google-auth==2.49.1
# via databricks-sdk
-googleapis-common-protos==1.73.1
- # via opentelemetry-exporter-otlp-proto-grpc
-grpcio==1.80.0
- # via opentelemetry-exporter-otlp-proto-grpc
h11==0.16.0
# via
# httpcore
@@ -75,7 +82,9 @@ idna==3.11
# httpx
# requests
importlib-metadata==8.7.1
- # via opentelemetry-api
+ # via
+ # mlflow-skinny
+ # opentelemetry-api
itsdangerous==2.2.0
# via flask
jinja2==3.1.6
@@ -93,36 +102,25 @@ markupsafe==3.0.3
# werkzeug
mcp==1.26.0
# via claude-agent-sdk
-mlflow-tracing==3.10.1
+mlflow-skinny==3.10.1
# via coda (pyproject.toml)
opentelemetry-api==1.40.0
# via
- # mlflow-tracing
- # opentelemetry-exporter-otlp-proto-grpc
+ # mlflow-skinny
# opentelemetry-sdk
# opentelemetry-semantic-conventions
-opentelemetry-exporter-otlp-proto-common==1.40.0
- # via opentelemetry-exporter-otlp-proto-grpc
-opentelemetry-exporter-otlp-proto-grpc==1.40.0
- # via coda (pyproject.toml)
opentelemetry-proto==1.40.0
- # via
- # mlflow-tracing
- # opentelemetry-exporter-otlp-proto-common
- # opentelemetry-exporter-otlp-proto-grpc
+ # via mlflow-skinny
opentelemetry-sdk==1.40.0
- # via
- # mlflow-tracing
- # opentelemetry-exporter-otlp-proto-grpc
+ # via mlflow-skinny
opentelemetry-semantic-conventions==0.61b0
# via opentelemetry-sdk
packaging==26.0
- # via mlflow-tracing
+ # via mlflow-skinny
protobuf==6.33.6
# via
# databricks-sdk
- # googleapis-common-protos
- # mlflow-tracing
+ # mlflow-skinny
# opentelemetry-proto
pyasn1==0.6.3
# via pyasn1-modules
@@ -132,8 +130,9 @@ pycparser==3.0
# via cffi
pydantic==2.12.5
# via
+ # fastapi
# mcp
- # mlflow-tracing
+ # mlflow-skinny
# pydantic-settings
pydantic-core==2.41.5
# via pydantic
@@ -142,13 +141,17 @@ pydantic-settings==2.13.1
pyjwt==2.12.1
# via mcp
python-dotenv==1.2.2
- # via pydantic-settings
+ # via
+ # mlflow-skinny
+ # pydantic-settings
python-engineio==4.13.1
# via python-socketio
python-multipart==0.0.22
# via mcp
python-socketio==5.16.1
# via flask-socketio
+pyyaml==6.0.3
+ # via mlflow-skinny
referencing==0.37.0
# via
# jsonschema
@@ -157,6 +160,7 @@ requests @ git+https://github.com/psf/requests@bc04dfd6dad4cb02cd92f5daa81eb562d
# via
# coda (pyproject.toml)
# databricks-sdk
+ # mlflow-skinny
rpds-py==0.30.0
# via
# jsonschema
@@ -165,19 +169,24 @@ simple-websocket==1.1.0
# via
# coda (pyproject.toml)
# python-engineio
+smmap==5.0.3
+ # via gitdb
+sqlparse==0.5.5
+ # via mlflow-skinny
sse-starlette==3.3.4
# via mcp
starlette==1.0.0
# via
+ # fastapi
# mcp
# sse-starlette
typing-extensions==4.15.0
# via
# anyio
- # grpcio
+ # fastapi
# mcp
+ # mlflow-skinny
# opentelemetry-api
- # opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-sdk
# opentelemetry-semantic-conventions
# pydantic
@@ -187,13 +196,16 @@ typing-extensions==4.15.0
# typing-inspection
typing-inspection==0.4.2
# via
+ # fastapi
# mcp
# pydantic
# pydantic-settings
urllib3==2.6.3
# via requests
uvicorn==0.42.0
- # via mcp
+ # via
+ # mcp
+ # mlflow-skinny
werkzeug==3.1.7
# via
# flask
diff --git a/static/index.html b/static/index.html
index bb7eecf..d27965e 100644
--- a/static/index.html
+++ b/static/index.html
@@ -142,6 +142,11 @@
border-color: rgba(100,150,255,0.25);
box-shadow: 0 0 8px rgba(100,150,255,0.1);
}
+ #session-count-label {
+ margin-left: auto; padding: 0 12px;
+ font-size: 11px; color: rgba(255,255,255,0.4);
+ white-space: nowrap; flex-shrink: 0;
+ }
#toolbar .font-size-row {
display: flex; align-items: center; gap: 4px;
}
@@ -375,6 +380,7 @@ General
+
@@ -383,6 +389,7 @@ General
+