Skip to content
KONIGI Patterns

AI Assistants / Memory and context / Custom instructions

‹ 3 of 5 ›

Custom instructions

People need to save preferences they would otherwise repeat in every conversation.

Updated September 12, 2026

Problem

People repeat the same preferences in each conversation. When they forget, they may have to rewrite the answer.

Solution

Let people save instructions that apply to future conversations. Keep these separate from preferences the assistant infers through memory.

Show when saved instructions are active, with a direct way to read or edit them. Include when they were last updated so people can spot old guidance.

Support project-specific instructions when people do different kinds of work. Explain which instructions take priority when they conflict. Encourage short instructions and make clear that the AI may still fail to follow them.

Use when

People use the assistant regularly and have preferences that apply across requests.

Don’t use when

Use is occasional or the account is shared. Instructions written by one person on a shared login silently reshape everyone else’s answers, and the people affected have no idea the block exists.

Trade-offs

Saved instructions reduce repetition, but forgotten rules can make answers confusing. Project rules and memory can conflict with them. Longer instructions also use more of the space available for the conversation.

Checklist

  • Can the viewer tell from a conversation that instructions are active?
  • Is the text reachable in one click from where it takes effect?
  • Is the date each instruction was written visible?
  • Do global and project instructions layer in a stated order?
  • Is there a budget or a warning as the block grows?
  • Does the interface promise determinism it cannot deliver?
  • What happens when an instruction conflicts with a stored memory?
  • Can instructions be turned off for one conversation without deleting them?
  • On a shared account, is it clear whose instructions are in force?
  • Are the instructions included when a conversation is shared or exported?

Compare

ChatGPT keeps a global instruction block and adds per-project instructions above it, so the layering is explicit in the interface rather than implied. Claude attaches the equivalent to projects, so the natural unit is a body of work rather than the account. That fits anyone who switches between roles. Gemini leans toward inferring preferences from past chats instead of asking for them, trading the audit trail for less setup. Notion puts the equivalent in the workspace as written documentation the assistant reads, so the standing instructions are also a thing colleagues can see and edit.

Memory chip shows preferences saved by the assistant. Scoped context determines which project instructions apply. Conversation history contains earlier answers shaped by those instructions.

First-run state can introduce the feature. Refusal may result when instructions rule out the requested answer.

Custom instructions anatomy A standing instruction block with each rule dated, a project layer above a global layer with the resolution order stated, a budget showing how much of every turn the block consumes, and an indicator on the conversation that instructions are in force. Written once, in force forever, forgotten by March This project Cite the source file for every claim 1 Sep Everywhere British English, no bullet lists 4 Feb I am a designer, not an engineer 4 Feb project overrides global spent on every turn, forever advisory rather than binding, and a long block dilutes itself 3 instructions active on the conversation, one click to the text 1 2 3 4 1 DATE EVERY RULE An answer that is strangely formal traces to a sentence written in February. 2 A STATED ORDER Code instructions are harmful when drafting an email. Layer, and say how. 3 A VISIBLE BUDGET The twentieth rule weakens the first nineteen and costs context every turn. 4 DO NOT PROMISE SETTINGS A model drifts from these and weighs them against the immediate request. On a shared login, one person's standing instructions reshape everyone else's answers silently.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

Written once, in force forever, forgotten by March. Each rule carries a date, the project layer overrides the global one, and the block spends part of every turn whether or not it still applies.

shadcn
npx shadcn@latest add button progress
Tokens
--card--card-foreground--muted--muted-foreground--border--status-warn--chart-1--scale-seq-3

This project

  • Cite the source file for every claim

Everywhere

  • British English, no bullet lists
  • I am a designer, not an engineer

project overrides global

100 of 400 characters, spent on every turn, forever

Standing guidance: advisory rather than binding, and a long block dilutes itself

CustomInstructions.tsxTwo scopes in a stated order, a date on every rule, a character budget computed from the text, and a chip that counts what is in force.

import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";

/** A rule the viewer wrote, and when. The date is what turns a standing order
 *  into something a person can audit six months later. */
export type Rule = { text: string; written: Date };

/** Two scopes, no more. Project sits above global and wins on conflict; the
 *  order is printed rather than left to be discovered. */
export type Scope = "project" | "global";
export type Layer = { scope: Scope; name: string; rules: Rule[] };

const SCOPE_ORDER: Scope[] = ["project", "global"];
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const day = (d: Date) => `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`;

type Props = {
  layers: Layer[];
  /** Characters the block may spend on each turn. Every rule is prepended to
   *  every conversation, so the cost is per turn and it never stops. */
  limit: number;
  /** The chip on the conversation opens the text. One click, no settings dive. */
  onOpen: () => void;
};

export function CustomInstructions({ layers, limit, onOpen }: Props) {
  const ordered = [...layers].sort((a, b) => SCOPE_ORDER.indexOf(a.scope) - SCOPE_ORDER.indexOf(b.scope));
  const rules = ordered.flatMap((l) => l.rules);
  const used = rules.reduce((n, r) => n + r.text.length, 0);
  const pct = Math.min(100, Math.round((used / limit) * 100));

  return (
    <div className="grid grid-cols-[1.1fr_0.9fr] gap-5 rounded-lg border bg-card p-4 text-[11px]">
      <div className="rounded-lg border bg-muted">
        {ordered.map((layer, i) => (
          <div key={layer.scope} className={i > 0 ? "border-t" : undefined}>
            <p className="px-3 pt-2.5 uppercase tracking-wide text-muted-foreground">{layer.name}</p>
            <ul className="pb-2.5">
              {layer.rules.map((r) => (
                <li key={r.text} className="flex items-baseline gap-3 px-3 pt-1.5">
                  <span className="text-card-foreground">{r.text}</span>
                  <time className="ml-auto tabular-nums text-muted-foreground" dateTime={r.written.toISOString()}>
                    {day(r.written)}
                  </time>
                </li>
              ))}
            </ul>
          </div>
        ))}
      </div>

      <div>
        <p className="text-muted-foreground">project overrides global</p>
        <Progress value={pct} className="mt-2 h-2.5 bg-muted [&>div]:bg-scale-seq-3" aria-label="context budget" />
        <p className="mt-1.5 text-muted-foreground">
          {used} of {limit} characters, spent on every turn, forever
        </p>
        {/* The box is not a settings panel and should not look like one. */}
        <p className="mt-4 leading-relaxed text-status-warn">
          Standing guidance: advisory rather than binding, and a long block dilutes itself
        </p>
      </div>

      <div className="col-span-2 border-t pt-4">
        <Button variant="ghost" size="sm" onClick={onOpen} className="h-7 rounded-md bg-chart-1/15 px-3 text-[11px] text-chart-1 hover:bg-chart-1/25 hover:text-chart-1">
          {rules.length} instructions active
        </Button>
      </div>
    </div>
  );
}

demo.tsxHow it is called: one project rule over two global ones, 99 characters of a 400 budget.

import { CustomInstructions } from "./CustomInstructions";

/**
 * One project rule over two global ones, each dated. 99 characters against a
 * 400 budget puts the bar at a quarter. The chip is what a conversation shows;
 * clicking it would open this panel.
 */
export default function Demo() {
  return (
    <CustomInstructions
      layers={[
        {
          scope: "project",
          name: "This project",
          rules: [{ text: "Cite the source file for every claim", written: new Date("2026-09-01") }],
        },
        {
          scope: "global",
          name: "Everywhere",
          rules: [
            { text: "British English, no bullet lists", written: new Date("2026-02-04") },
            { text: "I am a designer, not an engineer", written: new Date("2026-02-04") },
          ],
        },
      ]}
      limit={400}
      onOpen={() => {}}
    />
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

No screenshots yet for this pattern. Browse Products to see other examples.