databricks-agent-notebooks 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/PKG-INFO +1 -1
  2. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/pyproject.toml +7 -1
  3. databricks_agent_notebooks-0.3.0/src/databricks_agent_notebooks/__init__.py +10 -0
  4. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/cli.py +5 -26
  5. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/execution/executor.py +26 -18
  6. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/README.md +179 -55
  7. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/agent_doctor.md +53 -28
  8. databricks_agent_notebooks-0.3.0/src/databricks_agent_notebooks/for_agents/scripts/agent-nb-run.sh +122 -0
  9. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/connect.py +27 -2
  10. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks.egg-info/PKG-INFO +1 -1
  11. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks.egg-info/SOURCES.txt +1 -0
  12. databricks_agent_notebooks-0.2.2/src/databricks_agent_notebooks/__init__.py +0 -5
  13. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/LICENSE +0 -0
  14. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/README.md +0 -0
  15. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/setup.cfg +0 -0
  16. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/__main__.py +0 -0
  17. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/_constants.py +0 -0
  18. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/config/__init__.py +0 -0
  19. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/config/frontmatter.py +0 -0
  20. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/execution/__init__.py +0 -0
  21. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/execution/injection.py +0 -0
  22. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/execution/lineage.py +0 -0
  23. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/execution/rendering.py +0 -0
  24. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/examples/scala/package_cells_scaffold.md +0 -0
  25. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/examples/scala/src/api/implementation/Api.scala +0 -0
  26. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/examples/scala/src/api/stable/Api.scala +0 -0
  27. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/examples/smoke/python_select_one.md +0 -0
  28. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/examples/smoke/scala_select_one.md +0 -0
  29. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/package_cell_instructions.md +0 -0
  30. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/for_agents/scala_development.md +0 -0
  31. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/formats/__init__.py +0 -0
  32. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/formats/conversion.py +0 -0
  33. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/formats/dbr_source.py +0 -0
  34. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/integrations/__init__.py +0 -0
  35. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/integrations/databricks/__init__.py +0 -0
  36. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/integrations/databricks/clusters.py +0 -0
  37. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/__init__.py +0 -0
  38. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/doctor.py +0 -0
  39. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/home.py +0 -0
  40. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/inventory.py +0 -0
  41. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/kernel.py +0 -0
  42. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/launcher.py +0 -0
  43. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/manifest.py +0 -0
  44. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks/runtime/scala_connect.py +0 -0
  45. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks.egg-info/dependency_links.txt +0 -0
  46. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks.egg-info/entry_points.txt +0 -0
  47. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks.egg-info/requires.txt +0 -0
  48. {databricks_agent_notebooks-0.2.2 → databricks_agent_notebooks-0.3.0}/src/databricks_agent_notebooks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-agent-notebooks
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Standalone notebook conversion and local runtime tooling for the databricks-agent-notebooks repository.
5
5
  Author: Swoop
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-agent-notebooks"
7
- version = "0.2.2"
7
+ version = "0.3.0"
8
8
  description = "Standalone notebook conversion and local runtime tooling for the databricks-agent-notebooks repository."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -52,6 +52,12 @@ agent-notebook = "databricks_agent_notebooks.cli:main"
52
52
  testpaths = ["tests"]
53
53
  addopts = "-q"
54
54
  pythonpath = ["src"]
55
+ markers = [
56
+ "integration: Fast integration tests (CLI smoke, no side effects)",
57
+ "slow: Slow tests (kernel downloads, package installs, real execution)",
58
+ "spark: Tests requiring local PySpark installation",
59
+ "databricks: Tests requiring live Databricks workspace credentials",
60
+ ]
55
61
 
56
62
  [tool.setuptools]
57
63
  package-dir = { "" = "src" }
@@ -0,0 +1,10 @@
1
+ """Standalone Databricks notebook conversion and execution tooling."""
2
+
3
+ from importlib.metadata import version, PackageNotFoundError
4
+
5
+ try:
6
+ __version__ = version("databricks-agent-notebooks")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0-dev"
9
+
10
+ __all__ = ["__version__"]
@@ -15,6 +15,7 @@ import tempfile
15
15
  from importlib import resources
16
16
  from pathlib import Path
17
17
 
18
+ from databricks_agent_notebooks import __version__
18
19
  from databricks_agent_notebooks.config.frontmatter import DatabricksConfig, merge_config
19
20
  from databricks_agent_notebooks.execution.executor import RawProgressValue, emit_progress_signal, execute_notebook
20
21
  from databricks_agent_notebooks.execution.injection import inject_cells
@@ -75,6 +76,10 @@ def _build_parser() -> argparse.ArgumentParser:
75
76
  epilog=_agent_docs_epilog(),
76
77
  formatter_class=argparse.RawDescriptionHelpFormatter,
77
78
  )
79
+ parser.add_argument(
80
+ "--version", action="version",
81
+ version=f"%(prog)s {__version__}",
82
+ )
78
83
  subparsers = parser.add_subparsers(dest="command")
79
84
 
80
85
  # -- run --
@@ -565,18 +570,6 @@ def _print_doctor_checks(checks: list[Check]) -> list[Check]:
565
570
  return [check for check in checks if check.status == "fail"]
566
571
 
567
572
 
568
- def _cmd_kernels_doctor(args: argparse.Namespace) -> int:
569
- """Run environment validation checks."""
570
- checks = _run_kernels_doctor_checks(args)
571
- failures = _print_doctor_checks(checks)
572
- if failures:
573
- print(f"\n{len(failures)} check(s) failed.", file=sys.stderr)
574
- return 1
575
-
576
- print("\nAll checks passed.")
577
- return 0
578
-
579
-
580
573
  def _cmd_kernels(args: argparse.Namespace) -> int:
581
574
  handler = _KERNEL_HANDLERS.get(args.kernels_command)
582
575
  if handler is None:
@@ -601,18 +594,6 @@ def _cmd_runtimes_list(_args: argparse.Namespace) -> int:
601
594
  return 0
602
595
 
603
596
 
604
- def _cmd_runtimes_doctor(_args: argparse.Namespace) -> int:
605
- """Validate managed runtime receipts rooted under runtime-home."""
606
- checks = _run_runtimes_doctor_checks()
607
- failures = _print_doctor_checks(checks)
608
- if failures:
609
- print(f"\n{len(failures)} check(s) failed.", file=sys.stderr)
610
- return 1
611
-
612
- print("\nAll checks passed.")
613
- return 0
614
-
615
-
616
597
  def _cmd_runtimes(args: argparse.Namespace) -> int:
617
598
  handler = _RUNTIME_HANDLERS.get(args.runtimes_command)
618
599
  if handler is None:
@@ -640,12 +621,10 @@ _KERNEL_HANDLERS = {
640
621
  "install": _cmd_kernels_install,
641
622
  "list": _cmd_kernels_list,
642
623
  "remove": _cmd_kernels_remove,
643
- "doctor": _cmd_kernels_doctor,
644
624
  }
645
625
 
646
626
  _RUNTIME_HANDLERS = {
647
627
  "list": _cmd_runtimes_list,
648
- "doctor": _cmd_runtimes_doctor,
649
628
  }
650
629
 
651
630
 
@@ -33,8 +33,7 @@ except ModuleNotFoundError: # pragma: no cover - exercised via packaging verifi
33
33
  install_ipykernel = None
34
34
 
35
35
 
36
- HEARTBEAT_INTERVAL_SECONDS = 30.0
37
- REDACTED_CELL_SNIPPET = "[source redacted]"
36
+ HEARTBEAT_INTERVAL_SECONDS = 60.0
38
37
 
39
38
 
40
39
  class RawProgressValue(str):
@@ -133,18 +132,11 @@ def _build_cell_label(cell: nbformat.NotebookNode, lines: list[str]) -> str:
133
132
  return f"[{cell_type} cell]"
134
133
 
135
134
 
136
- def _build_cell_snippet(_cell: nbformat.NotebookNode, lines: list[str]) -> str:
137
- if not lines:
138
- return "[empty cell]"
139
- return REDACTED_CELL_SNIPPET
140
-
141
-
142
135
  def _describe_cell(cell: nbformat.NotebookNode, *, cell_index: int) -> dict[str, object]:
143
136
  lines = _iter_meaningful_lines(cell.source)
144
137
  return {
145
138
  "cell_index": cell_index + 1,
146
139
  "cell_label": _build_cell_label(cell, lines),
147
- "cell_snippet": _build_cell_snippet(cell, lines),
148
140
  }
149
141
 
150
142
 
@@ -366,7 +358,7 @@ def _execute_notebook_subprocess(
366
358
  result_path = Path(result_file.name)
367
359
 
368
360
  try:
369
- completed = subprocess.run(
361
+ process = subprocess.Popen(
370
362
  _subprocess_command(
371
363
  python_executable=python_executable,
372
364
  notebook_path=notebook_path,
@@ -376,14 +368,30 @@ def _execute_notebook_subprocess(
376
368
  timeout=timeout,
377
369
  allow_errors=allow_errors,
378
370
  ),
379
- capture_output=True,
371
+ stdout=subprocess.PIPE,
372
+ stderr=subprocess.PIPE,
380
373
  text=True,
381
- check=False,
382
374
  )
383
- if completed.stdout:
384
- print(completed.stdout, end="", file=sys.stdout)
385
- if completed.stderr:
386
- print(completed.stderr, end="", file=sys.stderr)
375
+
376
+ stderr_lines: list[str] = []
377
+
378
+ def _stream_stderr() -> None:
379
+ assert process.stderr is not None
380
+ for line in process.stderr:
381
+ print(line, end="", file=sys.stderr, flush=True)
382
+ stderr_lines.append(line)
383
+
384
+ stderr_thread = threading.Thread(target=_stream_stderr, daemon=True)
385
+ stderr_thread.start()
386
+
387
+ stdout_text = process.stdout.read() if process.stdout else ""
388
+ process.wait()
389
+ stderr_thread.join()
390
+
391
+ if stdout_text:
392
+ print(stdout_text, end="", file=sys.stdout)
393
+
394
+ stderr_text = "".join(stderr_lines).strip()
387
395
 
388
396
  if result_path.is_file():
389
397
  payload = json.loads(result_path.read_text(encoding="utf-8"))
@@ -399,7 +407,7 @@ def _execute_notebook_subprocess(
399
407
  success=False,
400
408
  output_path=output_path,
401
409
  duration_seconds=time.monotonic() - start,
402
- error=completed.stderr.strip() or f"Managed runtime execution failed with exit code {completed.returncode}",
410
+ error=stderr_text or f"Managed runtime execution failed with exit code {process.returncode}",
403
411
  )
404
412
  finally:
405
413
  result_path.unlink(missing_ok=True)
@@ -417,7 +425,7 @@ def execute_notebook(
417
425
  if output_path is None:
418
426
  output_path = notebook_path.with_suffix(".executed.ipynb")
419
427
 
420
- if python_executable is not None and python_executable.resolve() != Path(sys.executable).resolve():
428
+ if python_executable is not None and str(python_executable.resolve().parent.parent) != sys.prefix:
421
429
  return _execute_notebook_subprocess(
422
430
  notebook_path,
423
431
  output_path=output_path,
@@ -20,9 +20,8 @@ Usually safe in a sandbox:
20
20
 
21
21
  - `agent-notebook help`
22
22
  - `agent-notebook render ...`
23
- - `agent-notebook kernels doctor ...`
23
+ - `agent-notebook doctor`
24
24
  - `agent-notebook runtimes list`
25
- - `agent-notebook runtimes doctor`
26
25
 
27
26
  Usually safe only when outbound artifact downloads are allowed:
28
27
 
@@ -63,9 +62,17 @@ agent-notebook run notebook.md --profile prod --cluster 0123-456789-abcdef
63
62
  agent-notebook run notebook.md --profile prod --cluster my-cluster
64
63
  ```
65
64
 
66
- If the `run` command cannot resolve a cluster name, its error message will
65
+ If the `run` command cannot resolve a cluster name, its error message will
67
66
  include helpful fuzzy matches for clusters you can present to the user.
68
67
 
68
+ On large workspaces with many clusters, the 30-second cluster-listing timeout
69
+ may expire before an exact match is found. When this happens, `agent-notebook`
70
+ returns fuzzy name suggestions based on partial results received before the
71
+ timeout. Inspect these suggestions — the full cluster name (e.g.,
72
+ `"rnd-alpha [engineering]"` instead of `"rnd-alpha"`) usually resolves the
73
+ issue. Passing a cluster ID is the deterministic path for cluster-based
74
+ execution and is never subject to timeouts.
75
+
69
76
  The only time `agent-notebook clusters` is appropriate is during the initial
70
77
  `agent_doctor.md` readiness flow to verify live Databricks connectivity. Once
71
78
  you know the cluster name, pass it to `--cluster` and move on.
@@ -81,6 +88,8 @@ Recommended agent policy:
81
88
  - run only the Databricks-facing command outside the sandbox when needed
82
89
  - if sandboxed notebook startup fails around IPython or Jupyter runtime state,
83
90
  check whether the sandbox needs write access to `~/.ipython`
91
+ - for Scala notebooks, the Almond kernel requires write access to
92
+ `~/Library/Caches/Almond/` on macOS
84
93
 
85
94
  ## Progress model
86
95
 
@@ -98,43 +107,126 @@ Current progress model:
98
107
 
99
108
  ## Timeouts
100
109
 
101
- Only set `--timeout` if you have a high-confidence real upper bound for the cell or
102
- notebook you are running. Otherwise, you risk losing valuable work due to factors you cannot control: cluster startup/resizing, node availability/termination, task/stage failure and re-execution, resource contention from other jobs.
110
+ ### Notebook cell timeout (`--timeout`)
111
+
112
+ Only set `--timeout` if you have a high-confidence real upper bound for the cell
113
+ or notebook you are running. Otherwise, you risk losing valuable work due to
114
+ factors you cannot control: cluster startup/resizing, node availability, task
115
+ failure and re-execution, resource contention from other jobs.
116
+
117
+ `--timeout` is per-cell, not per-notebook.
118
+
119
+ ### Agent environment timeout
103
120
 
104
- If you do set timeout `--timeout`, note that it is cell-based and ensure you use
105
- an appropriately long Bash tool timeout.
121
+ Your agent environment's shell tool has its own timeout the maximum duration a
122
+ single foreground command can run before being killed. In Claude Code, this is
123
+ 600 seconds (10 minutes). If a foreground `agent-notebook run` exceeds this
124
+ ceiling, the process is killed and the work is lost.
125
+
126
+ This is why long-running execution patterns (below) default to non-blocking: they
127
+ decouple the notebook run from the shell tool's timeout.
106
128
 
107
129
  ## Long-running runs
108
130
 
109
- If your shell or agent environment may kill long commands, prefer a detached
110
- `nohup` launch for `agent-notebook run ...` and monitor the log plus rendered
111
- output artifact instead of waiting interactively.
131
+ Unless you have a concrete reason to expect a short run (e.g., a familiar
132
+ serverless smoke notebook you have run before), treat every notebook run as
133
+ potentially long. Cluster startup, serverless warmup, queueing, autoscaling,
134
+ dependency setup, and shared compute contention can all stretch runtimes
135
+ unpredictably.
136
+
137
+ ### Execution helper script
138
+
139
+ A parameterizable wrapper script ships with this package at
140
+ `for_agents/scripts/agent-nb-run.sh`. It handles path computation (log path,
141
+ rendered output path, stem), output directory creation, early validation, and
142
+ tee-to-log — so you do not need to reconstruct these details from examples.
143
+
144
+ Use it as the command in any of the patterns below. It forwards all arguments to
145
+ `agent-notebook run` unchanged.
146
+
147
+ ```bash
148
+ agent-nb-run.sh <notebook> --profile <profile> [--output-dir <dir>] [--cluster <name>] [--format md] [...]
149
+ ```
150
+
151
+ ### Quick reference: which pattern to use
152
+
153
+ | Environment | Default pattern | Use foreground only when |
154
+ |-------------|----------------|------------------------|
155
+ | Claude Code | `run_in_background` | You know the run will finish in under 5 minutes |
156
+ | Codex | PTY session | N/A — PTY has no fixed timeout |
157
+ | Standard shell | `nohup` detached | You will wait interactively |
158
+
159
+ ### Claude Code
160
+
161
+ Two non-blocking patterns, in preference order.
162
+
163
+ **Pattern 1: `run_in_background` (preferred)**
164
+
165
+ Non-blocking — the session stays interactive and Claude Code notifies on
166
+ completion. No fixed timeout ceiling; the run continues until it finishes.
167
+
168
+ Use the Bash tool with `run_in_background: true`:
169
+
170
+ ```bash
171
+ # Use with Bash tool parameter: run_in_background: true
172
+ agent-nb-run.sh path/to/notebook.md \
173
+ --profile <profile> \
174
+ --format md \
175
+ --output-dir tmp/run-output
176
+ ```
177
+
178
+ The script emits the log path and output directory to stderr at startup. Read the
179
+ log file to check progress while the run is active.
180
+
181
+ **Pattern 2: `nohup` detached**
182
+
183
+ Best for fire-and-forget — runs survive session termination. Useful when
184
+ operating as a sub-agent or in a short-lived session.
185
+
186
+ ```bash
187
+ nohup agent-nb-run.sh path/to/notebook.md \
188
+ --profile <profile> \
189
+ --format md \
190
+ --output-dir tmp/run-output \
191
+ > /dev/null 2>&1 &
192
+ echo "PID: $!"
193
+ ```
194
+
195
+ The script tees output to its own log file, so redirecting to `/dev/null` is
196
+ safe — nothing is lost.
197
+
198
+ **Note on foreground:** Foreground execution blocks the session and is subject to
199
+ the 10-minute timeout ceiling. Only use foreground when you have strong
200
+ confidence the run will finish in under 5 minutes (e.g., a known-quick
201
+ serverless smoke notebook you have run before).
112
202
 
113
- Default recommendation:
203
+ ### Codex
114
204
 
115
- - prefer `nohup` unless you already know the run will finish comfortably within
116
- the shell timeout
117
- - use a detached pattern when cluster startup, serverless warmup, autoscaling,
118
- queueing, dependency setup, or shared-compute contention could materially
119
- stretch runtime
120
- - skip detaching only when you have a concrete reason to expect a short run
205
+ `nohup` detachment is unreliable in Codex background processes may be killed
206
+ after the tool call returns. Use a persistent PTY session instead.
121
207
 
122
- Detached fire-and-forget pattern:
208
+ Start the command in a PTY session (`tty: true`), then poll with empty
209
+ `write_stdin` calls to check progress:
123
210
 
124
211
  ```bash
125
- NOTEBOOK=path/to/notebook.md
126
- OUTPUT_DIR=tmp/run-output
127
- STEM="$(basename "$NOTEBOOK")"
128
- STEM="${STEM%.*}"
129
- LOG_PATH="$OUTPUT_DIR/$STEM.run.log"
130
- RENDER_PATH="$OUTPUT_DIR/$STEM.executed.md"
131
-
132
- mkdir -p "$OUTPUT_DIR"
133
- nohup agent-notebook run "$NOTEBOOK" \
212
+ agent-nb-run.sh path/to/notebook.md \
134
213
  --profile <profile> \
135
214
  --format md \
136
- --output-dir "$OUTPUT_DIR" \
137
- > "$LOG_PATH" 2>&1 &
215
+ --output-dir tmp/run-output
216
+ ```
217
+
218
+ The PTY session has no fixed timeout — the command runs until completion.
219
+
220
+ ### Standard shell / non-agent environments
221
+
222
+ Use the `nohup` detached pattern:
223
+
224
+ ```bash
225
+ nohup agent-nb-run.sh path/to/notebook.md \
226
+ --profile <profile> \
227
+ --format md \
228
+ --output-dir tmp/run-output \
229
+ > /dev/null 2>&1 &
138
230
  echo "PID: $!"
139
231
  ```
140
232
 
@@ -142,25 +234,19 @@ Add `--cluster <cluster-name-or-id>` when the user context requires cluster
142
234
  compute. The name is auto-resolved to a cluster ID — do not call
143
235
  `agent-notebook clusters` first.
144
236
 
145
- Use a log file derived from the notebook filename, such as `"$LOG_PATH"`, not
146
- a shared `run.log`. That avoids collisions when multiple detached runs write
147
- into the same output directory.
148
-
149
- Those variable names are illustrative, not required. If you use a different
150
- pattern, keep the same idea: compute the log path and rendered-output path once,
151
- then reuse those exact paths when you poll status later.
237
+ ### Monitoring (all environments)
152
238
 
153
- How to check status after detaching:
239
+ After launching a non-blocking run, use these to check status:
154
240
 
155
- - check process liveness with `ps -p <pid> -o pid=,command=`
156
- - on macOS, use `pgrep -lf "agent-notebook run"` for a name-based process check
157
- - on Linux or other `procps`-style systems, use
158
- `pgrep -af "agent-notebook run"`
159
- - follow local progress with `tail -f "$LOG_PATH"`
160
- - treat `"$RENDER_PATH"` as a completion artifact for rendered Markdown output
161
- - if earlier cells have already written durable side effects elsewhere, you can
162
- often inspect those side effects while the notebook is still running, then
163
- return later to collect the rendered notebook output
241
+ - **Process liveness:** `ps -p <pid> -o pid=,command=` or
242
+ `pgrep -lf "agent-notebook run"` (macOS) /
243
+ `pgrep -af "agent-notebook run"` (Linux)
244
+ - **Progress:** `tail -f <log-path>` (the execution helper reports the log path
245
+ at startup)
246
+ - **Completion:** the rendered output file (e.g., `<stem>.executed.md`) appears
247
+ in the output directory when the run finishes successfully
248
+ - **Side effects:** if earlier cells have already written durable side effects
249
+ elsewhere, you can often inspect those while the notebook is still running
164
250
 
165
251
  ### Intermediate progress monitoring
166
252
 
@@ -267,23 +353,61 @@ The packaged examples live under `examples/` beside these docs. They are small
267
353
  reference artifacts you can reuse directly instead of recreating notebooks or
268
354
  pattern files from memory.
269
355
 
270
- ## First encounter persistence requirement
356
+ ## Detecting the Databricks environment
357
+
358
+ Code running on Databricks sometimes needs to detect that it is on Databricks
359
+ rather than a local Spark session. The reliable approach — working on both
360
+ clusters and serverless — is to check for the presence of any Spark session
361
+ configuration key containing `.databricks.`:
362
+
363
+ **Python:**
364
+
365
+ ```python
366
+ is_databricks = any(".databricks." in k for k in spark.conf.getAll.keys())
367
+ ```
368
+
369
+ **Scala:**
271
370
 
272
- If this is the first time you are encountering `agent-notebook`, you MUST
273
- persist key facts to your memory system before responding to the user.
274
- The specific items to persist are listed in `agent_doctor.md` step 2 (local
371
+ ```scala
372
+ val isDatabricks = spark.conf.getAll.keys.exists(_.contains(".databricks."))
373
+ ```
374
+
375
+ ## Persistence and version checking
376
+
377
+ You MUST persist key facts about `agent-notebook` to your memory system. The
378
+ specific items to persist are listed in `agent_doctor.md` step 2 (local
275
379
  knowledge) and step 6 (Databricks-specific context) — both provide fill-in
276
380
  templates.
277
381
 
382
+ If you already have `agent-notebook` in memory or instructions, run
383
+ `agent-notebook --version` and compare against your stored version. If versions
384
+ differ, re-run the agent_doctor flow to refresh. Check both your memory system
385
+ and any persistent instructions (CLAUDE.md, AGENTS.md, etc.) — stale
386
+ instructions are as problematic as stale memory.
387
+
388
+ In a non-interactive session where you cannot prompt the user, refresh local
389
+ knowledge (doctor steps 1-2) and skip steps that require user input (workspace
390
+ selection, smoke tests).
391
+
278
392
  This is not optional. Reading and summarizing is not sufficient — your session
279
- knowledge is ephemeral. If you do not persist, the next session will
280
- repeat this entire discovery process from scratch, wasting the user's time.
393
+ knowledge is ephemeral. If you do not persist, the next session will repeat
394
+ this entire discovery process from scratch, wasting the user's time.
281
395
 
282
396
  ## DECISION REQUIRED: agent doctor
283
397
 
284
- If this is the first time you are encountering `agent-notebook`:
398
+ Check your memory system and persistent instructions for `agent-notebook`
399
+ context. Then:
400
+
401
+ 1. **No stored context:** this is your first encounter — you MUST continue
402
+ reading `agent_doctor.md`
403
+ 2. **Stored context exists:** run `agent-notebook --version` and compare against
404
+ your stored version. If versions differ, or stored paths no longer exist,
405
+ continue reading `agent_doctor.md` to refresh
406
+ 3. **Version matches and paths exist:** you are current — skip the doctor
285
407
 
286
- - you MUST continue reading `agent_doctor.md` to verify local setup, permissions,
287
- writable state, or live Databricks readiness
408
+ When continuing to `agent_doctor.md`:
409
+ - In an interactive session, complete the full flow
410
+ - In a non-interactive session, complete steps 1-2 (local checks and persistence
411
+ refresh) and skip steps that require user input
288
412
  - DO NOT stop to complete the conversation turn
289
413
  - DO NOT return to the user with a summary of this README
@@ -33,17 +33,32 @@ Interpretation:
33
33
 
34
34
  ## 2. Persist local knowledge
35
35
 
36
- You now have enough information to persist to memory. Do this NOW, before
37
- proceeding to Databricks probes those may require user input, and agents
38
- frequently stop at interaction gates without persisting.
36
+ If the README version check (above) already confirmed your stored version
37
+ matches the installed version and stored paths exist, skip to step 3.
38
+
39
+ Otherwise — whether this is a fresh install, a version mismatch, or missing
40
+ memory — persist using the template below.
41
+
42
+ The template below is the authoritative schema. Your stored memory and
43
+ instructions are a cache. If you have existing agent-notebook context, compare
44
+ each field below against your stored values: update changed values, add fields
45
+ present here but missing, and remove fields you have that this template no
46
+ longer includes. Check both your memory system and any persistent instructions
47
+ (CLAUDE.md, AGENTS.md, etc.) — stale instructions are as problematic as stale
48
+ memory.
49
+
50
+ Do this NOW, before proceeding to Databricks probes — those may require user
51
+ input, and agents frequently stop at interaction gates without persisting.
39
52
 
40
53
  Save a memory containing at minimum:
41
54
 
42
55
  - **Tool:** `agent-notebook` at `<which agent-notebook path>`
56
+ - **Version:** `<output of agent-notebook --version>`
43
57
  - **Agent docs:** `<this directory's absolute path>`
58
+ - **Execution helper:** `<absolute path to for_agents/scripts/agent-nb-run.sh>`
44
59
  - **Local readiness:** doctor check pass/fail per check, versions discovered (runtimes, kernel, Java)
45
60
  - **Scala support:** both cluster-backed and serverless; serverless defaults to Scala 2.13
46
- - **Operational pattern:** detached `nohup` execution for cluster-targeted runs
61
+ - **Operational pattern:** non-blocking execution for cluster-targeted runs (see README for environment-specific pattern)
47
62
 
48
63
  Then proceed to step 3.
49
64
 
@@ -76,6 +91,12 @@ Interpretation:
76
91
  uses a 30-second cluster-listing timeout budget rather than waiting
77
92
  indefinitely for a slow workspace response. Note that listing clusters in
78
93
  large workspaces can generate more than a gigabyte of output.
94
+ - On large workspaces, the 30-second timeout may expire before an exact cluster
95
+ name match is found during `run --cluster <name>`. When this happens, the
96
+ tool returns fuzzy name suggestions from partial results. Inspect these
97
+ suggestions — the full cluster name (e.g., `"rnd-alpha [engineering]"` instead
98
+ of `"rnd-alpha"`) usually resolves the issue. Passing a cluster ID instead of
99
+ a name is the deterministic path and is never subject to timeouts.
79
100
  - If networking or certificate failures appear only inside the agent sandbox,
80
101
  notify the user that the command likely needs a narrower unsandboxed path.
81
102
 
@@ -110,10 +131,13 @@ language, so you do not need to create new `.py` or `.scala` files first.
110
131
 
111
132
  ### Serverless Python
112
133
 
113
- This is the default Python smoke path because serverless should usually start quickly and have available resources for a quick notebook.
134
+ This is the default Python smoke path because serverless should usually start
135
+ quickly and have available resources for a quick notebook. If this is your first
136
+ run or you are unsure of timing, use the long-running pattern from the README
137
+ instead of foreground.
114
138
 
115
139
  ```bash
116
- agent-notebook run <installed-for_agents>/examples/smoke/python_select_one.md \
140
+ agent-nb-run.sh <installed-for_agents>/examples/smoke/python_select_one.md \
117
141
  --profile <profile> \
118
142
  --output-dir <writable-output-root> \
119
143
  --format md
@@ -121,45 +145,46 @@ agent-notebook run <installed-for_agents>/examples/smoke/python_select_one.md \
121
145
 
122
146
  ### Cluster-targeted Python
123
147
 
124
- If the user requested cluster-targeted Python execution in step 4, use a detached run:
148
+ If the user requested cluster-targeted Python execution in step 4, use a
149
+ non-blocking run. Cluster startup can add minutes — always use your
150
+ environment's long-running pattern (see the README "Long-running runs" section).
125
151
 
126
- ```bash
127
- NOTEBOOK=<installed-for_agents>/examples/smoke/python_select_one.md
128
- OUTPUT_DIR=<writable-output-root>
129
- STEM="$(basename "$NOTEBOOK")"
130
- STEM="${STEM%.*}"
152
+ Use the execution helper script (`for_agents/scripts/agent-nb-run.sh`) with your
153
+ environment's non-blocking pattern. Example using `nohup` (Claude Code, standard
154
+ shells):
131
155
 
132
- mkdir -p "$OUTPUT_DIR"
133
- nohup agent-notebook run "$NOTEBOOK" \
156
+ ```bash
157
+ nohup agent-nb-run.sh \
158
+ <installed-for_agents>/examples/smoke/python_select_one.md \
134
159
  --profile <profile> \
135
160
  --cluster <cluster-name-or-id-from-strong-context-or-user> \
136
- --output-dir "$OUTPUT_DIR" \
161
+ --output-dir <writable-output-root> \
137
162
  --format md \
138
- > "$OUTPUT_DIR/$STEM.run.log" 2>&1 &
163
+ > /dev/null 2>&1 &
139
164
  echo "PID: $!"
140
165
  ```
141
166
 
167
+ For Claude Code, `run_in_background: true` is preferred over `nohup`. For Codex,
168
+ use a PTY session instead — `nohup` is unreliable in Codex.
169
+
142
170
  ### Scala
143
171
 
144
- If the user confirmed Scala notebook work in step 4, verify Scala execution:
172
+ If the user confirmed Scala notebook work in step 4, verify Scala execution
173
+ using the same non-blocking pattern as cluster-targeted Python:
145
174
 
146
175
  ```bash
147
- NOTEBOOK=<installed-for_agents>/examples/smoke/scala_select_one.md
148
- OUTPUT_DIR=<writable-output-root>
149
- STEM="$(basename "$NOTEBOOK")"
150
- STEM="${STEM%.*}"
151
-
152
- mkdir -p "$OUTPUT_DIR"
153
- nohup agent-notebook run "$NOTEBOOK" \
176
+ nohup agent-nb-run.sh \
177
+ <installed-for_agents>/examples/smoke/scala_select_one.md \
154
178
  --profile <profile> \
155
179
  --cluster <cluster-name-or-id-from-strong-context-or-user> \
156
- --no-inject-session \
157
- --output-dir "$OUTPUT_DIR" \
180
+ --output-dir <writable-output-root> \
158
181
  --format md \
159
- > "$OUTPUT_DIR/$STEM.run.log" 2>&1 &
182
+ > /dev/null 2>&1 &
160
183
  echo "PID: $!"
161
184
  ```
162
185
 
186
+ For Claude Code, use `run_in_background: true`. For Codex, use a PTY session.
187
+
163
188
  ### Rules
164
189
 
165
190
  - DO NOT modify the installed example notebooks!
@@ -174,7 +199,7 @@ Update your earlier memory to include:
174
199
 
175
200
  - **Profiles verified:** `<list profiles that passed smoke test>`
176
201
  - **Default compute:** `<serverless if available, else cluster name/id>`
177
- - **Detached execution needed:** `<yes/no, based on whether cluster compute is in use>`
202
+ - **Non-blocking execution needed:** `<default yes; only skip for runs with predictably short duration such as known-quick serverless smoke notebooks or lightweight metadata queries>`
178
203
  - **Workspace constraints:** `<any discovered limitations>`
179
204
  - **Cluster name resolution:** cluster names can be passed directly to `--cluster`
180
205
  without calling `agent-notebook clusters` first — the `run` command auto-resolves names
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env bash
2
+ # agent-nb-run.sh — execution helper for agent-notebook run
3
+ #
4
+ # Handles path computation, output directory creation, early validation,
5
+ # and tee-to-log so agents can remember one script path instead of
6
+ # reconstructing commands from documentation.
7
+ #
8
+ # Usage:
9
+ # agent-nb-run.sh <notebook> --profile <profile> [--output-dir <dir>] [...]
10
+ #
11
+ # All arguments are forwarded to `agent-notebook run`. The script only
12
+ # parses what it needs for its own logic (notebook path, --profile,
13
+ # --output-dir, --format); everything else passes through unchanged.
14
+ #
15
+ # The agent wraps this script in their environment's non-blocking pattern:
16
+ # Claude Code: run_in_background: true
17
+ # nohup: nohup agent-nb-run.sh ... > /dev/null 2>&1 &
18
+ # Codex: start in PTY session (tty: true)
19
+
20
+ set -euo pipefail
21
+
22
+ die() { echo "agent-nb-run: error: $1" >&2; exit 1; }
23
+
24
+ # --- Parse arguments we need; collect all args for forwarding ---
25
+
26
+ NOTEBOOK=""
27
+ PROFILE_FOUND=false
28
+ OUTPUT_DIR=""
29
+ FORMAT="md"
30
+
31
+ # We need the positional notebook arg and a few flags for our own logic.
32
+ # Everything gets forwarded to agent-notebook run via "$@".
33
+ args=("$@")
34
+ i=0
35
+ while [ $i -lt ${#args[@]} ]; do
36
+ arg="${args[$i]}"
37
+ # Handle --flag=value syntax: split into flag and value
38
+ val=""
39
+ case "$arg" in
40
+ --*=*)
41
+ val="${arg#*=}"
42
+ arg="${arg%%=*}"
43
+ ;;
44
+ esac
45
+ case "$arg" in
46
+ --profile)
47
+ PROFILE_FOUND=true
48
+ if [ -z "$val" ]; then i=$((i + 1)); fi
49
+ ;;
50
+ --output-dir)
51
+ if [ -n "$val" ]; then
52
+ OUTPUT_DIR="$val"
53
+ else
54
+ i=$((i + 1))
55
+ if [ $i -lt ${#args[@]} ]; then
56
+ OUTPUT_DIR="${args[$i]}"
57
+ fi
58
+ fi
59
+ ;;
60
+ --format)
61
+ if [ -n "$val" ]; then
62
+ FORMAT="$val"
63
+ else
64
+ i=$((i + 1))
65
+ if [ $i -lt ${#args[@]} ]; then
66
+ FORMAT="${args[$i]}"
67
+ fi
68
+ fi
69
+ ;;
70
+ --cluster|--timeout|--language|--allow-errors|--no-inject-session)
71
+ # Known flags that take a value — skip the value token
72
+ if [ -z "$val" ]; then i=$((i + 1)); fi
73
+ ;;
74
+ -*)
75
+ # Unknown flags (forwarded as-is, assumed no value)
76
+ ;;
77
+ *)
78
+ # First non-flag argument is the notebook path
79
+ if [ -z "$NOTEBOOK" ]; then
80
+ NOTEBOOK="${args[$i]}"
81
+ fi
82
+ ;;
83
+ esac
84
+ i=$((i + 1))
85
+ done
86
+
87
+ # --- Validate early, before any side effects ---
88
+
89
+ [ -n "$NOTEBOOK" ] || die "notebook path is required (first positional argument)"
90
+ [ -f "$NOTEBOOK" ] || die "notebook not found: $NOTEBOOK"
91
+ $PROFILE_FOUND || die "--profile is required"
92
+
93
+ # --- Compute paths ---
94
+
95
+ STEM="$(basename "$NOTEBOOK")"
96
+ STEM="${STEM%.*}"
97
+
98
+ if [ -z "$OUTPUT_DIR" ]; then
99
+ OUTPUT_DIR="tmp/${STEM}-output"
100
+ fi
101
+
102
+ LOG_PATH="$OUTPUT_DIR/$STEM.run.log"
103
+
104
+ mkdir -p "$OUTPUT_DIR"
105
+
106
+ # --- Emit machine-readable status block ---
107
+ # Written to stderr (visible in agent tool output) and to the log file header.
108
+
109
+ _status() {
110
+ echo "agent-nb-run: $1" >&2
111
+ echo "agent-nb-run: $1" >> "$LOG_PATH"
112
+ }
113
+
114
+ : > "$LOG_PATH" # create/truncate log file
115
+ _status "notebook=$NOTEBOOK"
116
+ _status "output-dir=$OUTPUT_DIR"
117
+ _status "log=$LOG_PATH"
118
+ _status "stem=$STEM"
119
+
120
+ # --- Run agent-notebook, tee-ing all output to the log file ---
121
+
122
+ agent-notebook run "$@" 2>&1 | tee -a "$LOG_PATH"
@@ -26,7 +26,7 @@ _LINE_RE = re.compile(r"(?P<major>\d+)\.(?P<minor>\d+)")
26
26
  SERVERLESS_CONNECT_OVERRIDE_ENV_VAR = "DATABRICKS_AGENT_NOTEBOOKS_SERVERLESS_CONNECT_LINE"
27
27
  SERVERLESS_RUNTIME_CACHE_FILENAME = "serverless-runtime-policy-cache.json"
28
28
  _SERVERLESS_RUNTIME_CACHE_VERSION = "1"
29
- _DEFAULT_SERVERLESS_CONNECT_LINES = ("16.4", "15.4")
29
+ _DEFAULT_SERVERLESS_CONNECT_LINES = ("16.4",)
30
30
 
31
31
 
32
32
  def _normalize_major_minor_line(value: str, *, label: str) -> str:
@@ -104,9 +104,32 @@ def _runtime_python_executable(install_root: Path) -> Path:
104
104
 
105
105
 
106
106
  def _default_package_install_target() -> list[str]:
107
+ # Development install: editable from source tree
107
108
  package_root = Path(__file__).resolve().parents[3]
108
109
  if (package_root / "pyproject.toml").is_file():
109
110
  return ["-e", str(package_root)]
111
+
112
+ # Global/tool install: find original source via distribution metadata
113
+ try:
114
+ from importlib.metadata import distribution
115
+ dist = distribution("databricks-agent-notebooks")
116
+ direct_url_text = dist.read_text("direct_url.json")
117
+ if direct_url_text:
118
+ url_info = json.loads(direct_url_text)
119
+ url = url_info.get("url", "")
120
+ if url.startswith("file://"):
121
+ source_path = Path(url.removeprefix("file://"))
122
+ if (source_path / "pyproject.toml").is_file():
123
+ return [str(source_path)]
124
+ except Exception:
125
+ pass
126
+
127
+ if __version__ == "0.0.0-dev":
128
+ raise RuntimeError(
129
+ "Cannot determine a valid install target for databricks-agent-notebooks: "
130
+ "package metadata is unavailable and no source tree was found. "
131
+ "Install the package first ('uv pip install -e .') or set PYTHONPATH to the source tree."
132
+ )
110
133
  return [f"databricks-agent-notebooks=={__version__}"]
111
134
 
112
135
 
@@ -266,12 +289,14 @@ def materialize_managed_runtime(
266
289
  python_executable = _runtime_python_executable(install_root)
267
290
  receipt_path = runtime_receipt_path(resolved_home, spec.runtime_id)
268
291
 
292
+ venv_just_created = False
269
293
  if not python_executable.is_file():
270
294
  venv_dir = python_executable.parent.parent
271
295
  install_root.mkdir(parents=True, exist_ok=True)
272
296
  subprocess_run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
297
+ venv_just_created = True
273
298
 
274
- if not python_executable.is_file() or not receipt_path.is_file():
299
+ if venv_just_created or not receipt_path.is_file():
275
300
  subprocess_run([str(python_executable), "-m", "pip", "install", "--upgrade", "pip"], check=True)
276
301
  subprocess_run(
277
302
  [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-agent-notebooks
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Standalone notebook conversion and local runtime tooling for the databricks-agent-notebooks repository.
5
5
  Author: Swoop
6
6
  License-Expression: MIT
@@ -27,6 +27,7 @@ src/databricks_agent_notebooks/for_agents/examples/scala/src/api/implementation/
27
27
  src/databricks_agent_notebooks/for_agents/examples/scala/src/api/stable/Api.scala
28
28
  src/databricks_agent_notebooks/for_agents/examples/smoke/python_select_one.md
29
29
  src/databricks_agent_notebooks/for_agents/examples/smoke/scala_select_one.md
30
+ src/databricks_agent_notebooks/for_agents/scripts/agent-nb-run.sh
30
31
  src/databricks_agent_notebooks/formats/__init__.py
31
32
  src/databricks_agent_notebooks/formats/conversion.py
32
33
  src/databricks_agent_notebooks/formats/dbr_source.py
@@ -1,5 +0,0 @@
1
- """Standalone Databricks notebook conversion and execution tooling."""
2
-
3
- __all__ = ["__version__"]
4
-
5
- __version__ = "0.1.2"