Skip to main content

A Self-Assembling AI Context

An agent's system prompt — role, available tools, recent memory — usually lives in application code, hand-maintained alongside whatever the agent object actually supports. Add a tool, forget to update the prompt, and the agent doesn't know it exists. Here the prompt is assembled by reading the agent itself, so it can't drift from what's actually there.

Building a Prompt Tree

prompt is a small tree of tagged text — Prompt Library covers the full API. The basic shape:

step1.stof
#[main]
fn main() {
  const ctx = prompt(tag = 'context');
  ctx.push(prompt('You are a helpful support agent.', 'role'));
  ctx.push(prompt('Refunds must be under $100.', 'policy'));
  pln(ctx as str);
}
Output

An Agent That Renders Itself

render() doesn't hardcode a list of tools — it asks the object which functions are tagged #[tool] and builds the entry for each one from that:

note

This pattern works really well with AI tool calling, where a different set of tools can be made available every time they are asked for by the agent. The actual tool call can then be in Stof as well.

agent.stof
Agent: {
  role: "You are a helpful support agent."

  #[tool]
  fn lookup_order(id: str) -> str { `order ${id}` }

  #[tool]
  fn issue_refund(id: str, amount: float) -> str { `refunded ${amount}` }

  memory: ["Customer asked about order #4471."]

  fn render() -> prompt {
      const ctx = prompt(tag = 'context');
      ctx.push(prompt(self.role, 'role'));

      for (const tool: fn in self.funcs('tool')) {
          ctx.push(prompt(tool.name(), 'tool'));
      }

      for (const entry: str in self.memory) {
          ctx.push(prompt(entry, 'memory'));
      }

      ctx
  }
}

#[main]
fn main() {
  pln(self.Agent.render() as str);
}
Output

render() never names lookup_order or issue_refund directly — it just asks for everything tagged #[tool]. Add a third tool function anywhere on Agent and it shows up in the rendered context automatically, with nothing else in this file touched. The prompt can't go stale, because it was never a separate thing to keep in sync in the first place.