FN-6820: document artifact registry workflows

Document the user-facing artifact registry workflow from agent tools through dashboard discovery and storage behavior.

- Describe agent artifact registration, listing, viewing, and mailbox notification semantics.
- Expand the Artifacts dashboard workflow with gallery counts, previews, task links, and loading/error/empty states.
- Clarify artifact registry storage, hydration scope, retention, and concept terminology.

Files changed:
 CONCEPTS.md             |  2 +-
 docs/agents.md          | 17 ++++++++++++++---
 docs/dashboard-guide.md | 16 ++++++++++++----
 docs/storage.md         | 15 +++++++++++----
 4 files changed, 38 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-6820

Fusion-Task-Lineage: 1d3be0f3-fb4a-4d7e-9470-2110435e83a4
This commit is contained in:
gsxdsm
2026-06-22 19:18:49 -07:00
parent ec1d29e818
commit e59d3d60e7
4 changed files with 38 additions and 12 deletions

View File

@@ -245,7 +245,7 @@ A persisted crash-safe marker (`tasks.transitionPending`) written in the same tr
One runtime expansion of a `foreach` template subgraph, bound to a single planned step (`Task.steps[i]`). Identity is deterministic — `<foreachNodeId>#<stepIndex>:<templateNodeId>` — so resume reconstructs the full instance set from the pinned step count without persisting the expansion itself. Each instance carries its own run-state (current node, rework count, baseline/checkpoint, and in worktree mode its branch and integration status) in its own persisted run-state table. The step count is pinned at expansion; a later disagreement with the live step list is a `pin-mismatch` failure, never a silent re-expansion. An instance's lifecycle writes flow through `store.updateStep` so `Task.steps[]` stays the physical projection sink for every existing consumer.
### Artifact
A persisted registry entry produced by agents, workflows, or tasks for reusable deliverables and intermediate products. Artifacts have a type (`document`, `image`, `video`, `audio`, or `other`), author attribution, optional task linkage, metadata such as MIME type/size, and either inline text `content` or a `uri`/path reference for externally stored media.
A persisted registry entry produced by agents, dashboard chat, workflows, or tasks for reusable deliverables and intermediate products. Artifacts have a type (`document`, `image`, `video`, `audio`, or `other`), author attribution, optional task linkage, metadata such as MIME type/size, and either inline text `content` or a `uri`/path reference for stored media. The artifact registry stores metadata for cross-agent discovery, while the dashboard surfaces task-linked and task-less entries in the Artifacts view's **Artifacts** gallery.
### parse-steps
A workflow graph node that reads a declared Artifact and runs a registry parser to write the canonical step list (`Task.steps[]`) — the only graph-side writer of steps. Built-in parsers are `step-headings` (the `### Step N:` convention, extracted byte-identically from the legacy regex, including the `(depends: N,M)` annotation) and `json-steps`; plugins contribute parsers under `plugin:<pluginId>:<parserId>`. Parsing failures fail closed to a routable `outcome:parse-error` rather than crashing. A parse-steps node must dominate (precede on all paths) any `foreach(source:"task-steps")`, and running one after a foreach has already expanded trips pin protection (an audited failure) so re-plan loops cannot desynchronize an expanded region.

View File

@@ -33,7 +33,18 @@ fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>]
- In dashboard model-loop chat (main chat, QuickChat, and room responders), typing `/skill:{name}` requests that skill for the current AI session and strips the slash token from the prompt sent to the model. The requested skill is still subject to the normal enabled/disabled execution-skill filters; CLI-agent-backed PTY chat keeps raw terminal input semantics and does not interpret this command.
- Dashboard chat and planning sessions with a scoped task store expose `fn_task_document_write` and `fn_task_document_read`; because neither lane has an ambient task, both tools require an explicit `task_id`.
- Agent workflow-routing tools follow an intent boundary: agents may select or change a task workflow only when the user explicitly requested that workflow or when the agent created the task. Executors must not call `fn_workflow_select` to reroute the task they are executing unless the task instructions or a user steering comment explicitly asks for the workflow change.
- Executor, heartbeat, and dashboard chat sessions expose artifact registry tools: `fn_artifact_register` publishes document/image/video/audio/other artifacts with inline `content` or a `uri`, `fn_artifact_list` discovers artifacts across agents/tasks with filters, and `fn_artifact_view` reads metadata plus inline content or URI references. Each successful registration sends a best-effort `system` → dashboard user inbox notification with artifact metadata; notification failures are logged but do not fail the registration. Planning sessions intentionally exclude artifact tools until they can thread the existing `MessageStore` dependency.
- Executor, heartbeat, and dashboard chat sessions expose artifact registry tools so agents can publish and inspect multi-type deliverables without relying on the dashboard gallery. Planning sessions intentionally exclude artifact tools until they can thread the existing `MessageStore` dependency.
### Artifact registry tools
Artifact tools operate on the shared artifact registry, so artifacts are visible across agents and tasks when the caller has the artifact ID or can discover it through filters.
- `fn_artifact_register` registers a `document`, `image`, `video`, `audio`, or `other` artifact with `title`, optional `description`, optional `mimeType`, optional inline text `content`, optional `uri`/path reference, and optional `taskId`. Tool callers should provide either inline `content` or a `uri`/path reference for media stored elsewhere. Executor/heartbeat sessions infer the registering agent as `authorId`; dashboard chat uses the `dashboard-chat` author and requires `task_id` because chat has no ambient task.
- `fn_artifact_list` lists artifacts across agents and tasks with optional `type`, `authorId`, `taskId`, `search`, `limit`, and `offset` filters. Dashboard chat's scoped variant requires `task_id` and otherwise supports `type`, `authorId`, `search`, `limit`, and `offset` for that task.
- `fn_artifact_view` fetches one artifact by `id`, returning registry metadata plus inline `content` when present or the stored `uri`/path reference for media artifacts.
- Successful registration emits a best-effort `system` → `user` inbox notification to `DASHBOARD_USER_ID` with `artifactId`, `artifactType`, `title`, `authorId`, and optional `taskId` metadata. Notification delivery failures are logged and must never fail or roll back the artifact registration.
For the user-facing gallery and notification UX, see [Artifacts View](./dashboard-guide.md#artifacts-view) and [Mailbox View](./dashboard-guide.md#mailbox-view). For storage layout and hydration semantics, see [Artifact registry](./storage.md#artifact-registry-fn-6777).
### Flags
@@ -134,13 +145,13 @@ V1 runtime action categories:
The engine classifies tool calls by behavior (not namespace alone):
- `file_write_delete`: built-in `write` / `edit`, plus persistent write helpers like `fn_task_document_write`, `fn_memory_append`, `fn_task_attach`
- `file_write_delete`: built-in `write` / `edit`, plus direct filesystem attach helpers like `fn_task_attach`; low-risk coordination/registration writes such as `fn_task_document_write` and `fn_artifact_register` are handled by the coordination-exempt/read-only allow-lists below rather than this category
- `command_execution`: built-in `bash` when not classified as mutating git
- `git_write`: mutating git shell commands run via `bash`
- `network_api`: external/network-facing tools (for example `fn_research_run`, `fn_research_cancel`, `fn_research_retry`, `fn_web_fetch`)
- `task_agent_mutation`: task/agent mutation tools (for example `fn_update_agent_config`, `fn_task_pause`, `fn_spawn_agent`; action-gate task-import/create tools like `fn_task_create`, `fn_delegate_task`, `fn_task_import_github`, and `fn_task_import_github_issue` use this category in action-gate evaluation)
- Dashboard permission editors now show per-category example tools sourced from `AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES` in `@fusion/core`, plus a read-only exempt-tools panel for coordination/messaging bypass tools.
- `none`: positively recognized read-only tools (`read`, `grep`, `find`, `ls`, list/show/get-style `fn_*` tools, plus permanent-agent coordination/task-creation helpers like `fn_task_create`, `fn_delegate_task`, `fn_task_import_github`, and `fn_task_import_github_issue`)
- `none`: positively recognized read-only tools (`read`, `grep`, `find`, `ls`, list/show/get-style `fn_*` tools, plus permanent-agent coordination/task-creation helpers like `fn_task_create`, `fn_delegate_task`, `fn_task_import_github`, and `fn_task_import_github_issue`). Artifact tools mirror `fn_task_document_write` in the shipped allow-lists: `fn_artifact_register`, `fn_artifact_list`, and `fn_artifact_view` are present in `READONLY_FN_TOOLS` and `COORDINATION_EXEMPT_TOOLS`, so registration is treated as coordination/registry publication instead of a broad mutation approval.
`bash` git-write heuristic in v1:

View File

@@ -397,6 +397,7 @@ Mailbox view shows inbox/outbox communication threads and unread state.
- Inbox renders one row per message (no sender-based collapsing)
- clicking a message in the Mail tab opens the task detail pane with full message content and conversation context
- reply rows in the mailbox modal can expand inline to show the replied-to message context for easier thread reading
- when an agent or dashboard chat session registers an artifact with `fn_artifact_register`, Fusion sends a best-effort `system` → user inbox message announcing the new artifact (for example, `New image artifact registered: <title>`) with metadata for `artifactId`, `artifactType`, `title`, `authorId`, and optional `taskId`; notification delivery is informational and never blocks or rolls back the artifact registration
- mailbox now includes an **Approvals** tab with pending and history filters (`approved` / `denied` / `completed`), approval detail context, and inline approve/deny actions for pending requests
- in the **Agents** tab, the agent selector now includes **All agents**, which shows one combined agent-to-agent stream (with sender + recipient labels); selecting a specific agent still shows Inbox/Outbox subtabs
- mailbox entry points now show unread/pending indicators: the desktop/tablet Header mailbox toggle shows a pending-approval dot first or an unread dot when unread mail exists without pending approvals, the mobile bottom-nav Mailbox tab carries the mobile badges/dots, and the compact Header actions overflow keeps a Mailbox entry only when the mobile bottom nav is disabled
@@ -552,21 +553,28 @@ For per-run aggregation, `GET /api/agents/:id/runs/:runId/cited-goals` returns `
## Artifacts View
Artifacts view aggregates task documents, project markdown files, and registered artifacts.
Artifacts view aggregates project markdown files, task documents, and registered artifacts. The dashboard title is **Artifacts**; the internal tab bar keeps the shipped **Project Files**, **Task Documents**, and **Artifacts** labels.
Features:
- Group task documents by task ID (with revision history metadata)
- Search documents across tasks
- Open project markdown files with inline preview
- Browse the **Artifacts** tab for media registered by agents, users, or the system across tasks
- Browse the **Artifacts** tab for registry media registered by any agent, dashboard chat/user action, or system tool across tasks
- Use the tab-count badges to see the current counts for Project Files, Task Documents, and Artifacts; the Artifacts badge reflects the loaded `GET /api/artifacts` result set, including active search filters
- Use the responsive media gallery to scan thumbnail-first image and video cards with consistent framing, while audio, document, and generic artifacts remain readable cards in the same grid
- Expand image and video artifact thumbnails into a full-size lightbox; dismiss it with the close button, backdrop click, or Escape while non-previewable artifact cards keep their normal controls and links
- Preview artifact images inline, play video and audio with native controls, read document previews, and open generic artifacts through their media URL
- Jump directly from a document group or artifact card to the owning task detail modal when a task is linked; inside task detail, the **Artifacts** tab shows that task's documents and registered media artifacts together
- Preview artifact images inline, play video and audio with native controls, read document previews from inline content/description, and open generic `other` artifacts through their media URL (`GET /api/artifacts/:id/media`)
- Read artifact metadata on each card: type badge (`Image`, `Video`, `Audio`, `Document`, or `Other`), title, optional description/content preview, author ID, timestamp, and linked task title/ID when present
- Use **Open task** on an artifact card to jump back to the originating task when the artifact has a `taskId`; inside task detail, the **Artifacts** tab shows that task's documents and registered media artifacts together
- Loading state: the Artifacts tab shows `Loading artifacts…` while the first artifact list request is pending and no artifact results are loaded
- Empty states: with no search query it shows `No artifacts yet.` plus the hint that artifacts are created by agents, users, and system tools; with a search query it shows `No artifacts match "<query>".`
- Error state: a failed artifact list request uses the shared `Failed to load artifacts: <error>` panel with a **Retry** action that re-runs the artifact fetch
- Toggle between raw text and rendered markdown using the **Markdown/Plain** button
- Highlight text in raw or rendered project-file previews, choose **Add comment**, and send the file path, selected snippet, and your comment to the **New Task** dialog
Agent registrations also surface through the [Mailbox View](#mailbox-view): successful `fn_artifact_register` calls send a best-effort system inbox notification so users can discover new media even before opening the gallery.
![Artifacts view](./screenshots/documents-view.png)
## Reports View

View File

@@ -65,11 +65,15 @@
### Artifact registry (FN-6777)
- `artifacts` is the first-class metadata registry for generated or uploaded task artifacts. Rows store title/description, media type, author identity, optional task linkage, metadata JSON, textual content, a relative URI, and size; binary bytes are not stored in SQLite.
- `artifacts` is the first-class metadata registry for generated or uploaded task artifacts. Rows store ID, `type` (`document`, `image`, `video`, `audio`, or `other`), title/description, MIME type, size, author identity/type, optional task linkage, metadata JSON, textual `content`, a relative `uri`, and timestamps; binary bytes are not stored in SQLite.
- `TaskStore.registerArtifact()` writes task-scoped binary payloads under `<rootDir>/.fusion/tasks/{ID}/artifacts/` and task-less registry payloads under `<rootDir>/.fusion/artifacts/`, then records a relative `artifacts/<file>` URI in SQLite. If the DB insert fails after a binary write, the store removes the orphaned file before surfacing the error.
- Inline text/document artifacts may store `content` directly in SQLite and therefore have no media file. The dashboard media route streams `GET /api/artifacts/:id/media` from disk when `uri` is present, or returns inline `content` with the persisted MIME type when no `uri` exists.
- `getArtifact(id)` returns metadata by ID, `getArtifacts(taskId)` returns active-task artifacts newest-first, and `listArtifacts(...)` is the cross-agent query path with type/author/task/search filters and pagination. List reads hide artifacts whose parent task is soft-deleted while preserving task-less artifacts.
- Task-linked artifact registration requires an active, non-archived task. Archived tasks are read-only for artifact writes; soft-deleted or missing tasks are rejected.
- Worktree DB hydration copies artifact metadata so isolated agents can query the registry shape locally, but binary payload files remain in the source project storage.
- Retention follows the existing task lifecycle rather than a separate artifact policy: soft-deleted parent tasks keep artifact rows/files for forensics but normal live-reader APIs hide them; hard deletion from the active `tasks` table cascades artifact metadata through the `taskId` foreign key, and archive cleanup removes the task directory that contains task-scoped artifact binaries. Task-less artifacts live under `<rootDir>/.fusion/artifacts/` and are not tied to task archival cleanup.
- Worktree DB hydration copies task-scoped artifact metadata for the current task/dependency graph alongside task rows and `task_documents`. It intentionally does not copy binary payload files, and it intentionally excludes task-less registry artifacts because dependency hydration is scoped to the active task graph.
Agent-facing registration tools are documented in [Artifact registry tools](./agents.md#artifact-registry-tools), and the dashboard browsing surface is documented in [Artifacts View](./dashboard-guide.md#artifacts-view).
### Task-ID integrity detection
@@ -426,6 +430,7 @@ The `tasks.cumulativeActiveMs` and `tasks.executionCompletedAt` columns are the
| `secrets` | Encrypted secret KV rows (`key` unique) with raw BLOB `value_ciphertext` + per-row random `nonce` (AES-256-GCM), per-secret `access_policy` CHECK (`auto`/`prompt`/`deny`), env-materialization metadata (`env_exportable`, `env_export_key`), and read-audit fields (`last_read_at`, `last_read_by`). Plaintext is never written to the database. |
| `task_documents` | Task-scoped document metadata/content keyed by `(taskId, key)` with current revision pointer. |
| `task_document_revisions` | Immutable revision history for task documents (content snapshots by revision). |
| `artifacts` | Artifact registry metadata for inline text and on-disk media artifacts. Stores type/title/description, MIME type/size, author identity, optional task linkage, metadata JSON, inline `content`, relative `uri`, and timestamps; binary media bytes live under task or registry `artifacts/` directories instead of SQLite. |
| `__meta` | Schema version + monotonic `lastModified` change detector, plus one-time bootstrap metadata such as `bootstrappedAt` and `projectIdentity`. |
| `goals` | Strategic intent records (`title`, optional `description`, `status`, timestamps) that can outlive mission timelines. |
| `mission_goals` | Many-to-many join between missions and goals with composite PK `(missionId, goalId)`, `createdAt`, and cascade-delete foreign keys to both parents. |
@@ -627,7 +632,8 @@ Fusion now auto-hydrates the worktree DB during executor startup at three points
Hydration copies only:
- current task row,
- transitive dependency task rows (BFS, depth cap 5, max 50 unique task IDs),
- `task_documents` rows for that same task-id set.
- `task_documents` rows for that same task-id set,
- task-scoped `artifacts` metadata rows for that same task-id set.
Implementation uses in-process SQLite streaming (`DatabaseSync`), source-side `SELECT`, destination-side `INSERT OR REPLACE` inside a destination transaction. Column lists are built from source/destination schema intersection (`PRAGMA table_info`), so schema drift degrades gracefully (dropped columns are logged once, and defaults apply on destination-only columns).
@@ -636,12 +642,13 @@ Example shape of the destination write:
```sql
INSERT OR REPLACE INTO tasks (<shared-columns...>) VALUES (<placeholders...>);
INSERT OR REPLACE INTO task_documents (<shared-columns...>) VALUES (<placeholders...>);
INSERT OR REPLACE INTO artifacts (<shared-columns...>) VALUES (<placeholders...>);
```
Expected executor log entry on success:
```text
Hydrated worktree DB: 4 tasks, 12 task_documents
Hydrated worktree DB: 4 tasks, 12 task_documents, 3 artifacts
```
A concrete recovered failure mode now covered by tests: when a worktree directory exists but its local `.fusion/` scratch state is missing, opening `DatabaseSync(<worktree>/.fusion/fusion.db)` can fail with `unable to open database file`. Hydration now performs destination bootstrap (`mkdir -p .fusion` + schema init) and retries the destination open once before degrading.