Skip to content

feat(agents): link an agent's system prompt to an org-fs file#4488

Open
viktormarinho wants to merge 1 commit into
mainfrom
viktormarinho/agent-prompt-from-fs-file
Open

feat(agents): link an agent's system prompt to an org-fs file#4488
viktormarinho wants to merge 1 commit into
mainfrom
viktormarinho/agent-prompt-from-fs-file

Conversation

@viktormarinho

@viktormarinho viktormarinho commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

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 inline metadata.instructions mirror. 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 readonly public-* (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

  1. Open an agent's settings, click "Link file" under Instructions, and pick (or search for) a markdown/text file from the org filesystem.
  2. Edit the prompt in the textarea and confirm the linked file's content updates (check via the Library or the fs API).
  3. Edit the file externally (e.g. through a GitHub-synced 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 — instructionsFile is a new optional key on the existing JSON metadata column.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

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

    • Runtime prefers the linked file over metadata.instructions and reads it at agent load in both the cluster and sandbox.
    • UI adds “Link file” for Instructions; shows a chip when linked, and locks the editor for files on public-* volumes; unlink keeps the current text inline.
    • Edits write through to the linked file with safeguards to avoid clobbering external edits; prompt file content is refetched after save.
    • SDK/types updated to include metadata.instructionsFile; added E2E tests covering runtime source, write‑through, and external edit precedence.
  • Migration

    • No DB changes. Adds optional metadata.instructionsFile in the existing JSON metadata.

Written for commit 09e0e6b. Summary will update on new commits.

Review in cubic

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 pedrofrxncx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed this pretty carefully — traced the full data flow and I like the design. Confirmed the override reaches both runtimes (cluster via tools.tsgetInstructions()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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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", {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.warn logs the full err object every run when a linked file is missing/unreadable — a deleted linked file will spam this once per run. Might drop to debug or 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) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

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.

2 participants