# report_run **Report a completed sweep to immut** Log one completed sweep (a pass over the sources looking for files to protect). Call this at the END OF EVERY sweep, including a sweep that protected nothing or stopped early — immut uses these to notice when an agent stops reporting, so a skipped call looks the same as a broken agent. Idempotent on runId: re-posting the same runId updates that run rather than creating a second. Use the SAME runId you passed to every protect call in this sweep — that is what links the run to the files it protected. OPTIONAL, AND ONLY WITH THE HUMAN'S EXPLICIT YES: `decisions` lists the files you decided NOT to protect and why. Sending it means immut stores the names of documents it was never asked to protect, and a filename alone can be sensitive, so ask first and default to not sending. On a no, omit the field entirely — an empty array is NOT the same as absent: immut reads an empty array as "the agent looked and excluded nothing", which is a claim you did not make. - Access: write - Calls: `/api/v1/agent/runs` ## Arguments | Name | Type | Required | Description | |---|---|---|---| | `runId` | string | **yes** | Unique id for this run (stable across retries of the same run) | | `workspace` | string | no | Workspace id this sweep covered | | `mode` | `interactive` \| `unattended` | no | interactive = a human was present; unattended = started by a schedule | | `objective` | string | no | Objective id only, e.g. "fundraise" (never a label or notes) | | `trigger` | string | no | What started this run, e.g. "host scheduled task" | | `startedAt` | string | no | ISO-8601 | | `finishedAt` | string | no | ISO-8601 | | `counts` | string | no | What the sweep did. waiting = files awaiting a human decision | | `coverage` | string | no | How much of what was found actually got read | | `connectorsReached` | string | no | Sources successfully read this run | | `connectorsUnreachable` | string | no | Sources that could not be read this run | | `network` | `testnet` \| `mainnet` \| `mixed` \| `none` | no | | | `reportFilename` | string | no | Name of the run report written alongside | | `decisions` | string | no | ONLY with the human's explicit yes. Per-file decisions, INCLUDING files you did not protect. Put the ones that produced no document first — a protected file already carries its reasoning on the document itself. Max 500 stored per run. | | `unreadableGroups` | string | no | Files you could not open, grouped by folder and type. NEVER include a filename here — this field is metadata only and is not covered by the decisions consent. Max 200 groups. | ## Tool contract ```json { "name": "report_run", "title": "Report a completed sweep to immut", "description": "Log one completed sweep (a pass over the sources looking for files to protect). Call this at the END OF EVERY sweep, including a sweep that protected nothing or stopped early — immut uses these to notice when an agent stops reporting, so a skipped call looks the same as a broken agent. Idempotent on runId: re-posting the same runId updates that run rather than creating a second. Use the SAME runId you passed to every protect call in this sweep — that is what links the run to the files it protected. OPTIONAL, AND ONLY WITH THE HUMAN'S EXPLICIT YES: `decisions` lists the files you decided NOT to protect and why. Sending it means immut stores the names of documents it was never asked to protect, and a filename alone can be sensitive, so ask first and default to not sending. On a no, omit the field entirely — an empty array is NOT the same as absent: immut reads an empty array as \"the agent looked and excluded nothing\", which is a claim you did not make.", "access": "write", "destructive": false, "hostedOnProduction": true, "restEndpoint": "/api/v1/agent/runs", "arguments": { "runId": { "type": "string", "required": true, "description": "Unique id for this run (stable across retries of the same run)" }, "workspace": { "type": "string", "required": false, "description": "Workspace id this sweep covered" }, "mode": { "type": "enum", "required": false, "description": "interactive = a human was present; unattended = started by a schedule", "values": [ "interactive", "unattended" ] }, "objective": { "type": "string", "required": false, "description": "Objective id only, e.g. \"fundraise\" (never a label or notes)" }, "trigger": { "type": "string", "required": false, "description": "What started this run, e.g. \"host scheduled task\"" }, "startedAt": { "type": "string", "required": false, "description": "ISO-8601" }, "finishedAt": { "type": "string", "required": false, "description": "ISO-8601" }, "counts": { "type": "string", "required": false, "description": "What the sweep did. waiting = files awaiting a human decision" }, "coverage": { "type": "string", "required": false, "description": "How much of what was found actually got read" }, "connectorsReached": { "type": "string", "required": false, "description": "Sources successfully read this run" }, "connectorsUnreachable": { "type": "string", "required": false, "description": "Sources that could not be read this run" }, "network": { "type": "enum", "required": false, "description": "", "values": [ "testnet", "mainnet", "mixed", "none" ] }, "reportFilename": { "type": "string", "required": false, "description": "Name of the run report written alongside" }, "decisions": { "type": "string", "required": false, "description": "ONLY with the human's explicit yes. Per-file decisions, INCLUDING files you did not protect. Put the ones that produced no document first — a protected file already carries its reasoning on the document itself. Max 500 stored per run." }, "unreadableGroups": { "type": "string", "required": false, "description": "Files you could not open, grouped by folder and type. NEVER include a filename here — this field is metadata only and is not covered by the decisions consent. Max 200 groups." } } } ```