feat(agents): link an agent's system prompt to an org-fs file#4488
feat(agents): link an agent's system prompt to an org-fs file#4488viktormarinho wants to merge 1 commit into
Conversation
Lets an agent's instructions be sourced from a linked markdown/txt file in the org filesystem (metadata.instructionsFile) instead of only the inline mirror: the runtime reads the file at load time (same async seam as the skills catalog), edits write through to the file, and files on readonly public-* (GitHub-synced) volumes lock the editor.
pedrofrxncx
left a comment
There was a problem hiding this comment.
Reviewed this pretty carefully — traced the full data flow and I like the design. Confirmed the override reaches both runtimes (cluster via tools.ts → getInstructions() → buildAgentSystemPrompt, and the sandbox/endpoint via virtual-mcp.ts), and the write-through gate (incoming ≠ stored mirror AND ≠ file) genuinely prevents stale-autosave clobber and link-time churn — I hand-traced all four E2E scenarios and they hold. Public-volume readonly is enforced in both the UI and the write-through, and unlink preserving inline text is a nice touch.
Leaving a few notes inline. None are blockers — mostly defense-in-depth / minor consistency stuff. The only one I'd love a quick decision on is the fs-write permission note, since it's latent rather than exploitable today. Great PR overall. 🙌
| .then((bytes) => new TextDecoder().decode(bytes)) | ||
| .catch(() => null); | ||
| if (fileText !== incomingInstructions) { | ||
| await ctx.orgFs.write( |
There was a problem hiding this comment.
Two small notes on the write-through, neither blocking:
1. Permission scope (defense-in-depth). This writes via ctx.orgFs.write(...) after only ctx.access.check() (the agent-update permission), whereas the HTTP fs PUT route gates writes on ORG_FS_WRITE. Since fileRef is fully caller-supplied, a user who can edit an agent but lacks fs-write could technically overwrite any file in the org by pointing instructionsFile at it. Today this is harmless because ORG_FS_WRITE is basic-usage (every member has it — see the ACL note at the top of org-fs.ts), so there's no live exploit. But that file explicitly anticipates per-volume ACLs, and this becomes a real bypass the moment they land. writeAgentPrompts is the precedent, but it writes a fixed agent-scoped path — this one's arbitrary. Might be worth a TODO here, or routing through the same ORG_FS_WRITE check.
2. No notifyOrgFsChange. The HTTP PUT route fires it so other Library viewers / the recent-files feed refresh live. This write skips it, so a prompt edit landing on the file won't propagate to other clients (the editing client self-invalidates its own query, but that's it). Minor.
| if (!orgFs) return null; | ||
| return new TextDecoder().decode(await orgFs.read(ref.volume, ref.path)); | ||
| } catch (err) { | ||
| console.warn("[virtual-mcp] failed to read linked instructions file", { |
There was a problem hiding this comment.
Not blocking — two minor observations:
- This runs a fresh org-fs read (S3 GET) on every client creation for file-linked agents, uncached. That's the correct tradeoff since "external edit wins" needs freshness (and it mirrors
skillsBlock), just flagging the per-run latency/cost. - The
console.warnlogs the fullerrobject every run when a linked file is missing/unreadable — a deleted linked file will spam this once per run. Might drop todebugor trim the payload.
| const [promptDraft, setPromptDraft] = useState<string | null>(null); | ||
| // Post-save echo: the refetched file matches the draft — drop it so later | ||
| // external file updates show through. Render-phase adjust, not an effect. | ||
| if (promptDraft !== null && promptFileText === promptDraft) { |
There was a problem hiding this comment.
Nit, not blocking: the draft-clear leans on exact string equality (promptFileText === promptDraft). It's safe today because the write and read round-trip verbatim (no normalization on either side), but if any normalization ever creeps into the fs write path, the draft would never clear and later external edits wouldn't surface in the editor. A one-line comment naming that dependency would save a future debugging session.
What is this contribution about?
Lets an agent's system prompt be sourced from a linked markdown/txt file in the org filesystem (
metadata.instructionsFile) instead of only the inlinemetadata.instructionsmirror. The runtime reads the linked file at agent-load time (same async seam as the skills catalog), so both the cluster engine and the sandbox/desktop daemon pick it up. Editing the prompt in the UI writes through to the file (guarded so a stale-mirror autosave never clobbers an external file edit), and files on readonlypublic-*(GitHub-synced) volumes lock the editor.Screenshots/Demonstration
No UI screenshots captured this session — the agent settings "Instructions" section gained a "Link file" control next to Improve/template actions; linking shows a chip with the file name (and a lock icon when the volume is readonly).
How to Test
public-*set) and reload the agent — confirm the new file content is served as the system prompt and the editor is locked read-only for public volumes.Migration Notes
No database migration —
instructionsFileis a new optional key on the existing JSONmetadatacolumn.Review Checklist
Summary by cubic
Link an agent’s system prompt to a markdown/text file in the org filesystem via
metadata.instructionsFile. The runtime reads the file at load time, edits write back to it, and public (GitHub‑synced) volumes are read‑only.New Features
metadata.instructionsand reads it at agent load in both the cluster and sandbox.public-*volumes; unlink keeps the current text inline.metadata.instructionsFile; added E2E tests covering runtime source, write‑through, and external edit precedence.Migration
metadata.instructionsFilein the existing JSON metadata.Written for commit 09e0e6b. Summary will update on new commits.