Prompts as Views

Keep growing Agent instructions readable by moving them into a prompt view: an ERB file that LittleGhost finds and renders for the Agent. The Agent prepares any application values the prompt needs, while the view holds the instructions sent to the model.

Start with the inline prompt

The Agent from Getting Started keeps its first instruction close to the model:

class CustomerSupportAgent < LittleGhost::Agent
  model "openrouter:openai/gpt-5.6-luna"
  system_prompt "Answer customer questions clearly and concisely."
end

Inline prompts are a good fit while the whole instruction is one thought.

Move a growing prompt into a view

Remove system_prompt from the class:

class CustomerSupportAgent < LittleGhost::Agent
  model "openrouter:openai/gpt-5.6-luna"
  tools HelpCenterLookupTool, OrderStatusTool
end

Then create app/prompts/customer_support/system_prompt.erb:

You help customers understand their orders and account.

Answer clearly and concisely.
Never invent company guidance. Check the help center when policy matters.
Use the order status tool before making a claim about a private order.

That is enough. LittleGhost turns CustomerSupportAgent into the logical path customer_support and looks for app/prompts/customer_support/system_prompt.erb.

The prompt is still a system instruction sent to the selected model provider. Keeping it in a view improves organization; it does not keep the content inside your process.

Prepare application values with Ruby

Define system_prompt as an instance method when the view needs values from your application. Assign those values to instance variables:

class CustomerSupportAgent < LittleGhost::Agent
  def system_prompt
    @company_name = "Northstar"
    @policy_version = SupportPolicy.current_version
  end
end

LittleGhost calls the method before it renders the view. The instance variables are available there by the same names:

You are a customer support agent for <%= @company_name %>.
Follow support policy <%= @policy_version %>.
Answer clearly and concisely.

If you know Rails controllers, the pairing is familiar: the method prepares values, and the ERB file presents them. The method’s return value is ignored. If it raises an exception, the Run fails in the usual way.

An inline system_prompt declaration still takes precedence over a view. Remove the inline declaration when you want LittleGhost to call the instance method and render system_prompt.erb.

Subclass actions can call super before adding their own values:

class BillingSupportAgent < CustomerSupportAgent
  def system_prompt
    super
    @billing_region = "US"
  end
end

Prompt views also receive three built-in local variables: invocation for the current request, run for its lifecycle, and agent for the run-scoped Agent. Use them when an instruction genuinely depends on the current request. Keep the customer’s wording in the caller message unless it belongs in the system instruction.

Every rendered value may be sent to the model provider. Put only data that belongs in the prompt into the view.

Share a small partial

Partials keep repeated instructions in one place. Create app/prompts/shared/_voice.erb:

Use a warm, direct voice for <%= @company_name %>.
Prefer one clear next step over a long list of possibilities.

Render it from the Agent’s system view:

You are a customer support agent for <%= @company_name %>.

<%= partial "shared/voice" %>

The underscore marks a partial. Partials share the Agent’s instance variables, so @company_name remains available without extra wiring.

Use an explicit local for a value that belongs only to one partial. For example, create app/prompts/shared/_closing.erb:

Offer one clear next step for <%= audience %>.

Then pass audience when you render it:

<%= partial "shared/closing", locals: {audience: "account owners"} %>

Locals do not flow into nested partials unless you pass them again.

Keep repeated renders predictable

LittleGhost calls system_prompt once for each file-backed prompt render. Each call starts with the instance variables captured after LittleGhost’s after_initialize callbacks finish. Values left behind by a later callback, Tool, or earlier render do not become prompt assigns by accident.

After preparing the view, LittleGhost restores the Agent’s live instance variables. The objects assigned to the view are still ordinary Ruby objects, though, so the view sees the same mutable objects. Treat them as read-only, or duplicate a value before assigning it when the template needs an isolated copy.

For state that should change across invocations, read from run, a session, or an application object in system_prompt instead of relying on an earlier prompt render.

Choose a different template path

Most named Agents can rely on their conventional path. Use system_template when a class should read a differently named view:

class BillingSupportAgent < LittleGhost::Agent
  system_template "customer_support/billing"
end

LittleGhost chooses one prompt source in this order:

  1. An inline system_prompt

  2. An explicit system_template

  3. The Agent’s conventional system_prompt.erb view

Applications can add prompt lookup roots through Configuration#prompt_paths. Earlier roots win, so an application can override a view from a shared prompt package.

Customize LittleGhost’s framework prompts

You can change LittleGhost’s own model-facing language without replacing its Ruby behavior. Tool descriptions, structured-result repair requests, context summaries, code-mode instructions, and other framework messages all come from bundled prompt views under the little_ghost/ namespace.

Decide when to customize

Models can respond differently to the same instruction, even when the Tool, schema, and task stay the same. Wording that works well with one model may lead another model to miss a Tool constraint, mishandle a repair request, or recover less reliably from a failed call. Adjusting the relevant framework prompt gives you a direct way to improve that interaction without forking LittleGhost.

An override can also align framework language with your application’s terminology and voice. Start with the narrowest template that covers the behavior you want to change, and evaluate it with the models your application uses. Keep protocol checks, authorization, and execution limits in Ruby; prompt wording guides the model but does not enforce those boundaries.

Find the prompt you want to change

Browse the catalog from your application:

$ little_ghost prompts list
$ little_ghost prompts list structured_output
$ little_ghost prompts list structured_output/repair
$ little_ghost prompts show structured_output/repair/request

The bare list command shows top-level features with template counts. Pass a feature or component path to browse its immediate children, or use list --all to print every template key. Keys follow feature/component/purpose. For example, built-in Tool descriptions live under tools/built_in, while JSON Schema feedback lives under tools/validation/schema. Directories named feedback contain conditions the model can correct. Directories named errors contain operational failures.

show prints the bundled ERB source. Every bundled template starts with a structured ERB comment that explains where LittleGhost renders it, its locals, and any formatting constraints. These headers use <%# ... -%>, so neither the comment nor its trailing newline enters the rendered prompt. Each interior line also starts with # so Ruby-aware editors recognize the whole header as a comment. A # line outside <%# ... -%> and an HTML comment are output text and would be sent to the model.

Bundled prompts use plain text and Markdown. When a prompt needs headings or structured sections, the default template uses Markdown headings and lists rather than XML-like wrapper tags.

Copy an override into your application

Copy one template into the default application prompt root before editing it:

$ little_ghost prompts copy structured_output/repair/request

That creates app/prompts/little_ghost/structured_output/repair/request.erb. Use --root when the application configures a different prompt root:

$ little_ghost prompts copy structured_output/repair/request --root config/prompts

Use --agent when one Agent needs different wording from the rest of the application. Pass the Agent’s logical path, which is its underscored class name without the Agent suffix:

$ little_ghost prompts copy structured_output/repair/request --agent customer_support

That creates app/prompts/customer_support/little_ghost/structured_output/repair/request.erb. Namespaced classes use a namespaced logical path, such as Admin::CustomerSupportAgent becoming admin/customer_support.

Copy the complete catalog when an application deliberately wants to own every piece of framework wording:

$ little_ghost prompts copy --all

Copy commands refuse to overwrite any existing destination. With --all, the command checks every destination before it starts writing. Copied templates retain their invisible documentation headers so the override remains self-explanatory in the application.

Choose the override scope

For every framework prompt that applies to an Agent invocation, LittleGhost uses the first matching template in this order:

  1. An invocation-specific TrustedPath root

  2. The Agent-scoped directory inside a configured prompt root

  3. The runtime-wide directory inside a configured prompt root

  4. The template bundled with LittleGhost

Each root keeps the same little_ghost/<key>.erb namespace. Agent scope inserts the logical path before that namespace.

Runtime-wide overrides normally live in the default app/prompts root. Add or replace configured roots when the application uses another location:

LittleGhost.configure do |config|
  config.prompt_paths = ["config/prompts", "app/prompts"]
end

The uncommon invocation-specific layer is explicit because it can change model instructions for only one call. From config/little_ghost.rb:

campaign_prompts = LittleGhost::TrustedPath.new(
  path: File.expand_path("campaign_prompts", __dir__)
)

CustomerSupportAgent.ask(
  "Where is my order?",
  template_paths: [campaign_prompts]
)

The invocation root contains little_ghost/structured_output/repair/request.erb, as a runtime root does. Direct LittleGhost.generate calls accept the same template_paths: option.

Know what an override can change

Framework templates control model-facing language, not protocol roles, Tool names, schemas, validation behavior, security checks, or execution limits. A required template cannot render as blank.

LittleGhost is pre-1.0. Template keys and their documented locals are maintained on a best-effort basis until 1.0, so review release notes and copied overrides when upgrading.

Treat views as application code

Prompt views run as ERB inside the Ruby process and can call Ruby. Keep prompt directories with the rest of your application code rather than letting a request choose one.

Use TrustedPath only when application code needs to select a request-specific root. Do not derive its path from a request parameter, model output, or Tool argument. The wrapper confirms that the directory exists and resolves symbolic links. It does not inspect who can edit the directory or restrict what its ERB can execute.

Build request-specific input in a Workflow

A prompt view defines reusable instructions for one Agent. A Workflow may still build request-specific input for that Agent:

invoke CustomerSupportAgent, input: <<~MESSAGE
  #{input.text}

  Verified research:
  #{research}
MESSAGE

The Workflow is composing this request. CustomerSupportAgent still receives its own system prompt view when it runs.

Continue with Tools to give those Agents application capabilities while keeping model input, trusted context, and delegated sandbox operations distinct.