Skip to content

[Feature]: A project log: every command run on a project, with its time, parameters and outcome #797

Closed

Description

@elpidioassinaturas

The need

A project should be able to answer "what was done here, when, and with what?". That serves reproducing an analysis, reviewing a student's or a colleague's work, or writing a paper's methods section. Today RustGIS keeps three partial records:

  • The History pane (Project::gp_history) keeps geoprocessing runs only: up to 500, saved with the project. They are undone with the project, so an undone Buffer leaves no trace.
  • Record Task keeps the steps the user chose to record, as a task, and skips anything holding a secret.
  • The MCP audit log (docs/mcp.md) keeps an agent's tool calls in the app-data folder, one file a day, not with the project.

None of them records the rest: symbology, labels, layers added or removed, edits, layouts, exports, cube and raster commands, or a script's or the assistant's commands. And none shows what was undone.

Proposal

A project log: one entry for each command run on the project, from the app, a script, the assistant, the CLI, Python or MCP, kept in the .rgis file.

Each entry:

Field What it holds
at when it started: UTC, in milliseconds since 1970
command its id (layer.symbology, gp.run, cube.trend)
params its parameters, with secrets removed and large values clipped (below)
ok whether it succeeded
error the error text when it failed, clipped
millis how long it took

New commands:

  • project.log {"since"?, "until"?, "command"?, "failed"?, "limit"?}: the entries, newest last. command is an id or a prefix (layer.).
  • project.exportLog {"path", "format"?: "csv" | "jsonl"}: writes the log out. CSV has one row per entry, with the parameters as a JSON column.
  • project.clearLog: empties it. It is undoable like any other change, and it leaves its own entry, so a cleared log says it was cleared.

In the app, the History pane gets a second view, Project Log, beside the geoprocessing runs. It has a filter by command and a Save As button for the CSV or JSONL. That UI is part 2, after the engine part below.

Choices in this proposal

  • Recorded in Session::run, at depth 0. Every front end reaches the project through it, so one place covers them all. A command run by another command (a script's lines, a model's steps) is inside its parent's entry: script.run is logged with its script, and gp.run with its tool and parameters.

  • Commands that change the project, or that reach a file or a service. Two kinds are logged:

    • every command with spec.mutates (all but those built with .view());
    • a .view() command whose parameters name a file, folder, address or connection, such as an export or project.save. These are the keys the plugin permission gate already checks (PLACE_KEYS in crates/engine/src/cmd/plugins.rs).

    Everything else is a .view() command that reads only: listing layers or commands, describing a tool, reading features, and view moves (map.goToXY, map.zoomIn). An agent or a script calls those hundreds of times, and they change nothing, so they are left out. ui.* commands never reach the engine. Undo and redo are logged (edit.undo with the label of the step it took back), so the log shows what was undone.

  • Outside the undo snapshots. The log is kept beside the project's undo history, not in it, and written into the .rgis on save. Undoing a command doesn't erase its entry, and an undo snapshot doesn't copy the log.

  • Secrets never written. The parameters go through the redaction History's Copy as code already applies (redacted in crates/engine/src/cmd/history.rs: addresses lose their keys and tokens, and a parameter named as a password, key, secret or token is emptied). An error that mentions a credential is withheld, as the audit log does.

  • Bounded. Each entry's parameters are clipped to 4 KB of JSON: a long string is cut, and an array of more than 100 items keeps its first 100 with a count. This matters for pasted GeoJSON and long scripts. The log keeps its last 10,000 entries, the oldest dropped first: at most 40 MB (10,000 × 4 KB), usually far less. Both limits are constants and stated in the docs.

  • On by default, and saved with the project. That matches History, which already saves its runs' parameters (with local paths) in the .rgis. A project field, logCommands (default true, set with project.logCommands {"on": bool}), turns it off, and project.clearLog empties it before a project is shared. The docs say that a shared .rgis carries its log.

  • Older projects: a project with no log opens with an empty one, and the field is skipped when it is empty, so a project that never logged anything saves as it does today.

  • Not a replacement for History. History keeps its outputs, its Copy as Python / Command / CLI and its Save as Python Script. The log is a plain record and runs nothing again.

Out of scope

  • Logging what isn't a command: pans and zooms with the mouse, a pane opened. Those are UI state, not changes to the project.
  • A log shared across projects or a user-wide log: the MCP audit log covers agents.
  • Replaying the log: a task (New Task From History) or History's script already do that for what can be run again.

Plan

  1. Engine: LogEntry, recording in Session::run, project.log, project.exportLog, project.clearLog, saving and loading in .rgis, and project.logCommands. Tests cover depth 0 only, undo kept, redaction, clipping, the cap, an old project opening, and CLI and Python runs logged. Docs go in docs/user-guide/data.md (projects) and docs/cli.md.
  2. App: the History pane's Project Log view, with its filter and Save As.

I can do both, as two PRs. Since this adds a field to the .rgis format, I'd like your go on the design first.

Activity

  1. Mavrick-F commented on Oct 28, 2026

    @Mavrick-F
    Member

    @giswqs @elpidioassinaturas

    When this issue was opened, it asked for a go on the design before the work started, since it adds a field to the .rgis. That go never came, and part 1 (#947) is now well along. Before more review rounds go into it, I think the design questions need an answer. The code isn't the problem: CI is green and the review has been thorough.

    Two open questions:

    1. Where the log lives and whether it's on by default. As written, every mutating command, every file or service access and every undo is written into the .rgis, by default and up to 10,000 entries. The .rgis is the file people share, so a shared project carries file paths, SQL text and service addresses unless someone knows to clear the log first. History already saves its runs' parameters there, so there's a precedent. This is a much larger record, though.
    2. How it relates to History and Record Task. With this PR, a project has three overlapping records of what was done (History, recorded tasks and the log), plus the MCP audit log outside it. I'd rather settle now whether the log is the one record that History and tasks become views of, or a separate fourth one, before the format commits to an answer.

    A way to merge most of the work now: split #947 so the first PR keeps the log for the session only, in memory, without writing it to the .rgis. Everything that doesn't depend on storage would land:

    • the Session::run hook and the rules for what's logged;
    • redaction and clipping;
    • the protections against plugins;
    • project.log, project.exportLog and project.clearLog;
    • most of the tests.

    Storing the log in memory removes the shared Arc<Mutex>, the relative paths and the cached save text, and the FORMAT question no longer comes up. Saving the log, in the .rgis, in a separate file or as part of a merged History, can follow once the questions above are settled, and nothing in the first PR would need undoing.

    giswqs, which way would you like this to go?

  2. elpidioassinaturas commented on Oct 30, 2026

    @elpidioassinaturas
    ContributorAuthor

    @Mavrick-F, agreed, the split is the better path. Part 1 is ready as a session-only log, held in memory on Session and never written to the .rgis, its recovery copy, a package or a template. It keeps:

    • the Session::run hook and its rules for what's logged;
    • redaction and clipping;
    • the plugin protections;
    • project.log, project.exportLog, project.clearLog and project.logCommands;
    • their tests.

    That drops the shared Arc<Mutex>, the relative paths, the cached save text and the FORMAT question, and project.rs is back as on main. Clearing or switching the log no longer changes the project either. The docs now describe the log as an audit trail of what was done in the session, not as a way to reproduce an analysis. Local CI passes.

    How the log gets saved, and how it relates to History and recorded tasks, stays open here. @giswqs, if the split works for you, I'll push it to #947 (it replaces the branch's log-in-the-project version) and update the PR text.

  3. deniial00 commented on Oct 31, 2026

    @deniial00
    Member

    I agree with @Mavrick-F's concerns and support the proposed session-only approach in #947.

  4. Mavrick-F commented on Oct 31, 2026

    @Mavrick-F
    Member

    @opengeos/rustgis-maintainers @elpidioassinaturas

    #947 (the session log) and #1181 (Diagnostics) are both close, and #1216 asks whether they should share a pane. So here's a picture of how the records fit together, which may also answer the two questions still open here.

    RustGIS will soon have several records of what happened. They differ mainly in their rules, not in how they look:

    Record Where it's kept On undo What it's for
    History (tool runs) in the .rgis goes back with the project how the project was made; runs again as code or as a task
    Session log (#947) in memory stays, and the undo gets its own entry everything done in the session
    Diagnostics (#1181) in memory stays what went wrong: errors, warnings, network failures
    MCP audit log app-data, a file a day stays what an outside agent did

    Tasks are different again: steps the user chose to keep and run again.

    Suggested answers to the two open questions:

    1. Where the log is saved: not in the .rgis. History already answers "how was this made" there, and with undo rules the log can't share. If people want the log kept across sessions, an opt-in file in app-data, like the MCP audit log, keeps the project file to the project's current state.
    2. How the session log relates to History and tasks: keep them as separate records. They can't be one list: an undone Buffer has to leave History (or Save as Python Script would redo work the user threw away), but it has to stay in the log. Tasks keep coming from History.

    The MCP audit log stays its own record too. It keeps reads as well as changes, for 30 days, and the user can't turn it off from inside a session, so it can't be a filtered view of the session log.

    One possible follow-up, not for #947: the log doesn't say who ran a command (the app, a script, the assistant, MCP, a plugin). Recording that would let it answer "what did the assistant change?". Session has no notion of a caller today, though, so each front end would have to declare itself. I think that's a good way to increase trust through transparency.

  5. elpidioassinaturas commented on Nov 4, 2026

    @elpidioassinaturas
    ContributorAuthor

    Done in two parts, both merged: #947 (the session log: every command run on a project, with its time, parameters and result) and #1324 (the History pane's Tool runs and Session log views, the log filtered and saved as CSV). Closing.

    🤖 Generated with Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions