Custom Instructions
NimbleBrain wraps any app’s user-supplied custom instructions in <app-custom-instructions> containment in the agent’s system prompt — automatically, on every conversation turn, for every active bundle. As an app author you opt in to that behavior by publishing a single MCP resource at a reserved URI.
This is the platform’s “bottom-up” pattern for per-app guidance: the host enforces the convention, the bundle owns everything else (storage, UI, tool name, validation).
The convention
Section titled “The convention”app://instructionsPublish this resource from your app’s MCP server. NimbleBrain reads it on every prompt assembly:
- Body is empty / resource not published → no overlay; agent sees nothing.
- Body has content → wrapped in
<app-custom-instructions>containment alongside your<app-instructions>(frominitialize.instructions) inside the Installed Apps section of the system prompt.
The body is treated as Markdown. There’s no schema; it’s freeform user-set text.
What you own
Section titled “What you own”Everything except the URI convention and the prompt-side wrapping:
- Storage. Where the body lives on disk (typically a file in your bundle’s data dir).
- The tool to write/clear. Any name you want —
set_brand_voice,configure_assistant,update_conventions, etc. The agent calls it; the user wires it from your settings UI. - The editor UI. Settings panel, modal, sidebar — your call. Validation, character cap, placeholder copy.
- Schema. If you want typed config (brand voice + tone + persona as separate fields), serialize to Markdown for the resource body. Or publish multiple resources (e.g.
app://instructions/voice,app://instructions/persona) — but onlyapp://instructionsflows into containment automatically.
What the platform owns
Section titled “What the platform owns”- The URI. Bundle authors don’t pick a scheme; just publish at
app://instructions. - Containment escape. If a saved body happens to contain a literal
</app-custom-instructions>closing tag, the platform HTML-escapes it before wrapping so it can’t break out of containment. Prompt-injection mitigation — you don’t need to handle it on the bundle side. - Read on every assembly. Bodies are read fresh per chat turn (no caching), so edits apply mid-conversation without restart.
- Visibility gating. Tool-only servers that don’t publish the resource are naturally excluded — no flag to set, no negotiation.
Minimal example (Python / FastMCP)
Section titled “Minimal example (Python / FastMCP)”from pathlib import Pathimport os
INSTRUCTIONS_FILE = Path(os.environ.get("MPAK_WORKSPACE", "./workspace")) / "custom-instructions.md"
@mcp.resource("app://instructions", mime_type="text/markdown")def custom_instructions() -> str: """User-set custom instructions for this app.
NimbleBrain reads this on every prompt assembly and wraps a non-empty body in `<app-custom-instructions>` containment in the system prompt. """ return INSTRUCTIONS_FILE.read_text(encoding="utf-8") if INSTRUCTIONS_FILE.exists() else ""
@mcp.tool()async def set_custom_instructions(text: str) -> dict[str, str]: """Save custom instructions for this app. Empty clears.""" if text == "": INSTRUCTIONS_FILE.unlink(missing_ok=True) return {"status": "cleared"} INSTRUCTIONS_FILE.parent.mkdir(parents=True, exist_ok=True) INSTRUCTIONS_FILE.write_text(text, encoding="utf-8") return {"status": "saved"}That’s it. Once installed in a workspace, anything saved via set_custom_instructions reaches the agent on every turn.
Settings panel (optional but recommended)
Section titled “Settings panel (optional but recommended)”If you want a UI surface where the user edits their instructions (versus only the agent setting them via tool call), declare a settings placement in your manifest:
{ "_meta": { "ai.nimblebrain/host": { "placements": [ { "slot": "settings", "resourceUri": "ui://my-app/settings", "label": "My App", "icon": "list-checks", "priority": 100 } ] } }}Then publish the panel HTML at ui://my-app/settings and call your tools via the iframe bridge. NimbleBrain shows the panel under Settings → This Workspace → Apps → My App.
See the Placements & Navigation page for slot details and the MCP App Bridge page for iframe → tool call wiring.
Why this convention
Section titled “Why this convention”- Discoverability. Every bundle that wants instructions support uses the same URI. Users don’t have to learn per-app conventions; agents don’t have to discover per-bundle write tools to know “where do my instructions live for this app.”
- Security. Containment escape happens platform-side, uniformly. Bundle authors writing the prompt-injection mitigation is something that will be gotten wrong over time.
- Composability. The pattern stacks with
<app-instructions>(your bundle author’s static guidance) and the host’s org/workspace overlays. The system prompt layers cleanly: org-wide policy → workspace policy → per-app author guidance → per-app user instructions. - Bottom-up. Your bundle decides if it supports custom instructions and how rich the UX is. The platform doesn’t synthesize anything you don’t opt into.
Related
Section titled “Related”- Workspace and org-level overlays — managed by the host, not by your bundle. Set via the
instructions__write_instructionssystem tool or the workspace/org settings UI. - Manifest reference — full
_meta["ai.nimblebrain/host"]schema. - Placements & Navigation — slots including
settingsfor your editor panel.