Prompt Mode Design: Full, Task, Minimal, and None

🌐 한국어

A well-built agent usually has a large system prompt. Persona, response style, tool conventions, skill catalog, memory usage, team collaboration rules, security boundaries. The main agent talking to a user needs all of it.

The problem is that the same agent also runs in other situations. Does a heartbeat job checking "any new mail?" every five minutes need 20,000 tokens of persona? Does a subagent that summarizes one file and exits need the team collaboration rules?

It doesn't. But most implementations have exactly one prompt, so they send it every time. This article is about splitting a prompt into size tiers by situation.

Why Four Tiers

In practice, four modes is where things converge.

  • Full — the main agent talking directly to a user. Every section.
  • Task — automation. Less personality needed, but it has to actually get work done, so tool- and skill-related sections stay.
  • Minimal — periodic checks, simple repetitive work. The bare minimum.
  • None — one identity line. For pure transformation work (translation, classification) where a prompt gets in the way.

Teams often start with two (full/minimal) and have Task force its way in later. Minimal was built to strip personality, but doing that also strips skill search and the execution-oriented instructions, and suddenly the task agent replies "I'll take care of that" and does nothing. Task fills that gap.

Treat Modes as Ordered Values

Leave modes as plain strings and you can't ask "which of these two is more restrictive?" Give them a rank.

type PromptMode string

const (

    PromptFull    PromptMode = "full"    // main agent — all sections

    PromptTask    PromptMode = "task"    // automation — lean but capable

    PromptMinimal PromptMode = "minimal" // subagent/periodic — reduced

    PromptNone    PromptMode = "none"    // identity line only

)

var modeRank = map[PromptMode]int{

    PromptFull: 3, PromptTask: 2, PromptMinimal: 1, PromptNone: 0,

}

// Return the more restrictive of two modes

func minMode(a, b PromptMode) PromptMode {

    if modeRank[a] <= modeRank[b] {

        return a

    }

    return b

}

minMode is needed because more than one party gets a say in the mode. When the agent's config says minimal but the run kind calls for task, the more restrictive one must win. If the configuration said "keep it light," the run kind shouldn't override that and make it heavier.

Resolving the Mode: Four Layers

The actual decision walks several sources in priority order.

// Priority: runtime override > run-kind auto-detect > agent config > default

func resolvePromptMode(runtimeOverride PromptMode, sessionKey string,

    configMode PromptMode) PromptMode {

    // Layer 1: an explicit caller override always wins

    if runtimeOverride != "" {

        return runtimeOverride

    }

    // Layer 2a: heartbeat — a simple check, so cap at minimal

    if isHeartbeatSession(sessionKey) {

        if configMode != "" {

            return minMode(configMode, PromptMinimal)

        }

        return PromptMinimal

    }

    // Layer 2b: subagent/scheduled — cap at task.

    //   Memory stays slim, but skill search and execution bias must survive

    //   or the run won't actually do anything.

    if isSubagentSession(sessionKey) || isCronSession(sessionKey) {

        if configMode != "" {

            return minMode(configMode, PromptTask)

        }

        return PromptTask

    }

    // Layer 3: per-agent configuration

    if configMode != "" {

        return configMode

    }

    // Layer 4: default

    return PromptFull

}

The key detail is that layer 2 is a ceiling, not a mandate. Because it goes through minMode, an already-more-restrictive config is respected. Auto-detection never overrides configuration to make the prompt heavier. That directionality matters — automatic rules stay predictable only when they can only reduce.

Section Gating — Assemble from Four Flags

The assembly function expands the mode into four booleans and lets each section declare its own condition.

func BuildSystemPrompt(cfg Config) string {

    isFull    := cfg.Mode == PromptFull || cfg.Mode == ""

    isTask    := cfg.Mode == PromptTask

    isMinimal := cfg.Mode == PromptMinimal

    var lines []string

    // Identity — every mode, including None

    lines = append(lines, identityLine(cfg))

    // Persona — full/task only. Automation still needs some character

    if (isFull || isTask) && len(cfg.PersonaFiles) > 0 {

        lines = append(lines, buildPersonaSection(cfg.PersonaFiles)...)

    }

    // Execution bias — full/task.

    // "Don't just describe a plan; call a real tool this turn"

    if (isFull || isTask) && cfg.HasTools {

        lines = append(lines, buildExecutionBiasSection()...)

    }

    // Tool call style (don't expose internal tool names, etc.) — full only.

    // Pointless for runs no user will ever read

    if isFull && cfg.HasTools {

        lines = append(lines, buildToolCallStyleSection()...)

    }

    // Skills — full/task. Pinned inline, everything else via search

    if (isFull || isTask) && cfg.HasSkills {

        lines = append(lines, buildSkillsHybridSection(cfg.PinnedSkills, cfg.HasSkillSearch)...)

    } else if isMinimal && cfg.PinnedSkills != "" {

        // Minimal gets the pinned-skill summary only, no search guidance

        lines = append(lines, buildPinnedSkillsMinimalSection(cfg.PinnedSkills)...)

    }

    return strings.Join(lines, "\n")

}

What makes this work is that adding a section declares its condition right there. Keep four separate per-mode templates instead and every new section means editing four places — which will drift.

What to Cut — the Deciding Question

For each section, ask: "without this, does the run fail, or does it just get less elegant?"

  • Fails → keep it: tool usage, execution bias, safety boundaries, definition of done.
  • Less elegant → cut it: tone guides, humor calibration, emoji policy, conversational style. A cron job doesn't need to fail wittily.
  • Invisible to users → cut it: instructions like "never expose tool names to the user" are meaningless in a run no human reads.

The third one is the most valuable in practice. A substantial fraction of most prompts is guidance about how to appear in front of a user — and background runs have no user.

Caution 1: Cut Too Much and It Stops Working

Here's the trap people fall into chasing token savings. Trim the prompt aggressively and the model reverts to conversation mode. Given "clean up these files," it doesn't call a tool — it replies "Sure, I'll clean those up. What criteria should I use?" and stops.

The execution bias section is what prevents this. It's only a few lines, but it must survive in automation modes.

## Execution Bias

If the user asks you to do work, start doing it in the same turn.

Use a real tool call when the task is actionable; do not stop at a plan or a promise-to-act reply.

Commentary-only turns are incomplete when tools are available and the next action is clear.

This is exactly why Task exists separately from Minimal. Automation doesn't need personality but does need drive.

Caution 2: It Interacts with Caching

If you also use prompt caching, there's an interaction you have to know about. Caching works on an exact match of the prompt's leading region, and changing the mode changes the content of that stable region. Which means every mode change creates a new cache entry.

Two practical rules:

  • Don't let the mode wobble within a session. If your architecture recomputes the mode per turn, pin it off the session key so input can't change the outcome.
  • Assume one cache per mode. An agent mixing three modes has three cache entries. Check that each sees enough traffic to be reused — if not, you're paying creation cost three times over.

Caution 3: Don't Ship Gating Without a Preview

Introduce section gating and seeing the prompt that actually goes out gets hard. Once a dozen conditions interlock, "what exactly is this agent's task-mode prompt?" isn't answerable by reading code.

So build a preview function that takes a mode and returns the finished prompt from day one. Wire it into an admin screen or a CLI and debugging time drops sharply — plus you can compare real token counts per mode instead of guessing.

// Take an agent and a mode, return the final prompt.

// Shared by the admin UI, the CLI, and tests.

func BuildPreviewPrompt(ctx context.Context, agent *AgentData,

    mode PromptMode, userID string, deps PreviewDeps) PreviewResult

Tests build on the same function. Pin rules like "team sections must not appear in minimal mode" as substring assertions, and you'll catch a section leaking through when someone adds one later.

The Real-World Effect

How much you save depends on your prompt's composition, but the trend is clear: the more an agent invests in persona, style guides, and team rules, the more the lower modes save. And these background runs are typically the high-frequency ones — a heartbeat every five minutes runs 288 times a day, far more often than user conversations do.

So the largest token savings sit exactly where no human is watching, which is precisely where you can cut the most. That's a fortunate alignment.

Summary

  • Split the prompt into Full / Task / Minimal / None — two tiers isn't enough
  • Give modes a rank and combine them with minMode
  • Auto-detection acts only as a ceiling — it never overrides config upward
  • Use per-section gating, never copies of a template per mode
  • The test: "does it fail without this, or just get less elegant?" — runs invisible to users need no style guidance
  • Keep execution bias in automation modes — cut it and the agent stops doing work
  • Mind the caching interaction — pin the mode within a session
  • Build the preview function first

Prompt modes ultimately come down to one question: "who is present for this run?" With a person there, how it looks matters. Without one, only what it accomplishes matters. Trim the prompt by exactly that difference.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

Go로 자동매매 신호봇 프레임워크 설계하기