Skip to content

feat(backend): file-authoring skills — write-file / edit-file / run-file + notebook cell edit/delete/rerun (closes #85) - #175

Merged
lucastononro merged 1 commit into
staging-v0.0.5from
feat/85-file-authoring-skills
Jul 29, 2026
Merged

feat(backend): file-authoring skills — write-file / edit-file / run-file + notebook cell edit/delete/rerun (closes #85)#175
lucastononro merged 1 commit into
staging-v0.0.5from
feat/85-file-authoring-skills

Conversation

@lucastononro

@lucastononro lucastononro commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Closes #85

What

Splits file authoring from code execution per the issue spec — purely additive, no signature changes to execute-code or the existing notebook skills.

New file skills (backend/skills/):

  • write-file(path, content, overwrite=true) — wraps services.volume.write_to_volume; emits file_created (new) / file_updated (overwrite); does not write into scripts/ — authoring is not an execution step.
  • edit-file(path, mode, ...)mode=replace: exact-anchor substitution (old must match exactly once, else informative error); mode=overwrite: whole-file escape hatch. Emits file_updated, never file_created.
  • run-file(path, heavy=false) — executes an existing workspace file via runpy.run_path(path, run_name='__main__') through services.sandbox.run_code (same preamble as execute-code: src/ on sys.path, session cwd). Auto-saves a one-line runpy call to scripts/step_NN_run_<name>.py — the audit log records what ran, the body stays on disk. heavy picks the training sandbox profile with the owner max_timeout cap (integrated with feat(agent): agent-selectable GPU/CPU per execute-code call with project allowance #160's compute allowance).

New notebook skills:

  • edit-notebook-cell / delete-notebook-cell — wrap new notebook_store.edit_cell / delete_cell helpers (built on the existing _lock(key) + save(...) pattern next to append_cell; edit preserves outputs exactly like apply_source_update). Both broadcast notebook.structure.changed so the UI updates live.
  • rerun-notebook-cell — looks a cell up by cell_id, re-executes its current source against the persistent kernel via kernel_manager.execute_and_wait (kernel cell_started clears outputs; earlier-cell variables stay in scope). Same result envelope as run-notebook-cell.

Shared plumbing: resolve_session_path() in services/skills/state.py — accepts session-relative, /sessions/{sid}/-absolute, and /data/sessions/{sid}/-prefixed paths; rejects escapes.

Agent YAMLs (all 6: chat, eda, data_prep, feature_eng, trainer, orchestrator): six new skills listed alongside execute-code; the descriptive "Workspace is a real Python repo" block replaced by the prescriptive "Authoring vs running — use the right verb" block from the spec §4 (rule of three; edit don't repaste; import from src/ works inside a kernel cell), per-agent example bullets kept; eda gains the §5 third-cell-paste guidance.

execute-code SKILL.md now states the one-shot contract (handler/schema/auto-save unchanged).

Frontend (minimal): file_updated added to the typed SSE discriminated union (FileUpdatedData, same wire shape as FileCreatedData) + handled in useSessionStream (tree update in place, no canvas auto-open, added to NON_VISIBLE_EVENTS). The flash-on-update and src/-pinned-open explorer polish from spec §7 is deferred — the event handling is the load-bearing part.

Tests

  • 48 new backend tests: test_write_file.py, test_edit_file.py, test_run_file.py, test_notebook_store.py, test_edit_notebook_cell.py, test_delete_notebook_cell.py, test_rerun_notebook_cell.py — happy + failure paths (invalid path, overwrite=false clobber, anchor not found / ambiguous anchor, missing file, cell_id not found, markdown-cell rerun, kernel timeout, edit_cell output preservation, delete_cell volume persistence, path resolution).
  • Full backend suite: 647 passed, 8 skipped (baseline 599 + 48 new).
  • ruff check + ruff format --check (0.15.22): clean (220 files).
  • Frontend: vitest 20/20, tsc --noEmit clean, next lint clean, prettier clean.

Out of scope (per spec)

delete-file, cross-session src/ promotion, fuzzy patching / unified diffs, cell reordering, multi-cell rerun.

Greptile Summary

Adds file-authoring and file-execution skills plus in-place notebook cell management.

  • Introduces write-file, edit-file, and run-file with workspace path resolution, sandbox execution, audit scripts, and file SSE events.
  • Adds notebook cell edit, delete, and rerun operations backed by notebook-store persistence and persistent kernels.
  • Registers the new skills across all agents and documents the authoring-versus-execution workflow.
  • Adds frontend handling for file_updated events and backend coverage for the new capabilities.

Confidence Score: 2/5

The PR should not merge until authoring is prevented from corrupting audit scripts, failed file runs are reported as failures, and notebook mutations are persisted in a consistently ordered critical section.

The new authoring surface can modify records intended to describe executed code, run-file masks ordinary nonzero exits at the tool-protocol level, and overlapping notebook saves can persist an older snapshot after a newer edit or deletion.

Files Needing Attention: backend/skills/write-file/handler.py, backend/skills/edit-file/handler.py, backend/skills/run-file/handler.py, backend/services/notebook_store.py

Important Files Changed

Filename Overview
backend/services/skills/state.py Adds bounded conversion among session-relative, volume-absolute, and sandbox-absolute workspace paths.
backend/skills/write-file/handler.py Adds workspace file creation and overwrite events, but does not enforce the documented scripts/ audit boundary.
backend/skills/edit-file/handler.py Adds exact-anchor and whole-file editing, while likewise permitting modification of scripts/ audit entries.
backend/skills/run-file/handler.py Adds sandboxed runpy execution and audit logging, but reports nonzero subprocess exits as successful tool calls.
backend/services/notebook_store.py Adds cell edit and deletion, with persistence occurring outside the mutation critical section and allowing out-of-order saves.
backend/skills/rerun-notebook-cell/handler.py Looks up and re-executes an existing code cell through the persistent kernel with bounded output rendering.
frontend/src/lib/types.ts Extends the typed SSE union with a file_updated payload matching file_created.
frontend/src/lib/useSessionStream.ts Inserts or backfills updated files in the tree without triggering the create-only canvas auto-open behavior.

Sequence Diagram

sequenceDiagram
    participant A as Agent
    participant S as Skill handler
    participant V as Session volume
    participant X as Sandbox/Kernel
    participant U as UI via SSE
    A->>S: write/edit/run file or notebook cell
    S->>V: Resolve and persist workspace state
    alt run-file
        S->>V: Write scripts/ audit entry
        S->>X: Execute runpy wrapper
        X-->>S: stdout, stderr, returncode
    else rerun notebook cell
        S->>X: Execute current cell source
        X->>V: Stream and persist outputs
    end
    S-->>U: file/notebook update event
    S-->>A: Tool result
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(backend): split file authoring from..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Context used:

  • Context used - backend/skills/AGENTS.md (source)

…/ edit-file / run-file + notebook cell edit/delete/rerun (closes #85)

New skills (purely additive; no signature changes to execute-code or the
existing notebook skills):

- write-file(path, content, overwrite=true) — wraps volume.write_to_volume;
  emits file_created / file_updated; does NOT write into scripts/ (authoring
  is not an execution step).
- edit-file(path, mode=replace|overwrite, old/new|content) — exact-anchor
  replace (must match exactly once) or whole-file overwrite; emits
  file_updated, never file_created.
- run-file(path, heavy=false) — runpy.run_path(path, run_name='__main__')
  through services.sandbox.run_code under the existing preamble (src/ on
  sys.path, session cwd); auto-saves a one-line runpy audit entry to
  scripts/step_NN_run_<name>.py; heavy picks the training sandbox profile
  with the owner max_timeout cap (same as execute-code post-#160).
- edit-notebook-cell / delete-notebook-cell — wrap new
  notebook_store.edit_cell / delete_cell helpers (outputs preserved on
  edit, same as apply_source_update; both persist under the existing
  _lock + save path); broadcast notebook.structure.changed for live UI.
- rerun-notebook-cell — looks a cell up by cell_id, re-executes its
  current source against the persistent kernel via
  kernel_manager.execute_and_wait (kernel cell_started clears outputs;
  earlier-cell variables stay in scope); same result envelope as
  run-notebook-cell.

Shared resolve_session_path() helper in services/skills/state.py
(session-relative, volume-absolute, and /data/-prefixed paths; rejects
escapes). All six skills registered in chat, eda, data_prep, feature_eng,
trainer, orchestrator YAMLs, whose 'Workspace is a real Python repo'
blocks are replaced by the prescriptive 'Authoring vs running' block
(rule of three; edit don't repaste; import from src/ works in kernel
cells) keeping the per-agent example bullets. execute-code SKILL.md now
documents its one-shot contract. Frontend: file_updated added to the
typed SSE union + handled in useSessionStream (tree update, no
auto-open).

Tests: 48 new (skill handlers happy/failure paths incl. invalid path,
non-matching/ambiguous anchor, missing file, cell_id not found, markdown
rerun, kernel timeout; notebook_store edit_cell output preservation +
delete_cell volume persistence; path resolution). Full backend suite:
647 passed, 8 skipped. ruff 0.15.22 check + format clean. Frontend:
vitest 20/20, tsc, next lint, prettier clean.
@lucastononro
lucastononro merged commit bc45dff into staging-v0.0.5 Jul 29, 2026
1 check passed
@lucastononro

Copy link
Copy Markdown
Owner Author

Merged into integration branch staging-v0.0.5 — closing.

Comment on lines +38 to +39
try:
path, _ = resolve_session_path(session_id, raw_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Audit scripts remain writable

When write-file or edit-file targets a path under scripts/, path resolution accepts it and the handler overwrites the file, allowing authored content to alter the audit record of what actually ran.

Context Used: backend/skills/AGENTS.md (source)

Fix in Claude Code

Comment on lines +208 to +213
output = result["stdout"]
if result["returncode"] != 0:
output = (
f"Exit code {result['returncode']}.\n"
f"STDOUT:\n{result['stdout']}\nSTDERR:\n{result['stderr']}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nonzero exits report success

When the executed file exits nonzero, the handler only formats the error output and still returns a successful tool result, causing the agent to continue as though a failed script completed successfully.

Fix in Claude Code

cell.pop("execution_count", None)
_cache[key] = nb

await save(session_id, name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Notebook saves can reorder

When an edit or deletion overlaps another notebook mutation or kernel completion, each operation releases the notebook lock before its volume upload finishes, allowing an older snapshot to land last and restore a deleted cell or discard newer source and output changes.

Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant