Cortex · guide

An agent’s prompt

Cortex puts together what a model is told, once per run, from parts that each come from whoever knows them. This file builds an agent from those parts and prints what it would send. It calls no model.

Install it
pnpm add @niscorp/cortex @niscorp/signal zod openai
Step 1 of 6

The model

createSignal makes the client for a provider, here Groq. Nothing in this file calls it: a preview only asks the client what the provider can do, so it needs no key.

The model
import { z } from 'zod';
import { createSignal } from '@niscorp/signal';
import { defineAgent, defineTool } from '@niscorp/cortex';
import type { Producer } from '@niscorp/cortex';

const llm = createSignal('groq');
Step 2 of 6

A tool with a guide

A tool’s description and input schema are sent with the tool. How to use it well goes in its guide, which Cortex puts in the prompt of every agent that has the tool, so an agent’s instructions never have to describe it.

A tool with a guide
const seatsLeft = defineTool({
  id: 'seatsLeft',
  name: 'seatsLeft',
  description: 'How many seats are left on a night',
  input: z.object({ night: z.string() }),
  guide: 'Pass the night as YYYY-MM-DD. A night with no show answers left: 0.',
  execute: ({ night }) => ({ night, left: night === '2026-10-24' ? 113 : 120 }),
});
Step 3 of 6

What the app knows

A producer is a function that returns what goes in the prompt. It is given the run’s deps, which are typed: here, the programme the app has read from its database.

It runs once, when a run starts. A string it returns becomes one system message, and an array of strings one message each.

What the app knows
type Deps = { programme: { night: string; title: string }[] };

const onStage: Producer<Deps> = ({ deps }) => deps.programme.map((show) => `${show.night}: ${show.title}`);
Step 4 of 6

The agent

instructions is the system prompt, and can be a function of deps too. context lists the agent’s producers: a string of its own, and the one above.

The schema is the contract for the answer, so there is no prose about the JSON to write. What a field must be goes in its .describe(), and Cortex sends the schema with it.

The agent
const Order = z.object({
  night: z.string().describe('The night, as YYYY-MM-DD'),
  seats: z.number().int().min(1).max(6),
});

const boxOffice = defineAgent<z.infer<typeof Order>, Deps>({
  id: 'box-office',
  instructions: 'Read the order out of the message. Check the night with the seatsLeft tool.',
  context: ['Doors open thirty minutes before the show.', onStage],
  tools: [seatsLeft],
  output: { schema: Order },
});
Step 5 of 6

What the run adds

producers on a run come after the agent’s own. An app uses them for what every agent should know at that moment, such as who is asking, without changing any agent.

What the run adds
const programme = [
  { night: '2026-10-24', title: 'The Tempest' },
  { night: '2026-10-25', title: 'Twelfth Night' },
];

const preview = await boxOffice.preview('Two seats for Sunday, please.', {
  llm,
  deps: { programme },
  producers: ['The member asking is Grace.'],
});
Step 6 of 6

Print it

preview returns the messages a run would start with, in the order Cortex puts them. Signal’s entry for Groq’s default model says it does not keep a tool’s arguments to their schema, so the answer is written as plain JSON (emit) and the schema goes in the prompt as OUTPUT SCHEMA.

The first four messages are the same on every run of this agent. They come first so that a provider’s prompt cache holds them.

Print it
console.log('answer travels by:', preview.strategy);
for (const message of preview.messages) {
  const text = typeof message.content === 'string' ? message.content : JSON.stringify(message.content);
  console.log(message.role.padEnd(7), text.split(/\s+/).slice(0, 6).join(' '));
}
Then

Run it.

It calls no model and needs no key. First the instructions, then the tool’s guide, the schema, and how to finish, which Cortex writes for the way the answer travels. Then the agent’s producers, the run’s, and the question as the user’s message.

Run it
npx tsx prompt.mts
It prints
answer travels by: emit
system  Read the order out of the
system  TOOL GUIDES — how to use
system  OUTPUT SCHEMA — your output MUST
system  FINISH PROTOCOL: Use your tools as
system  Doors open thirty minutes before the
system  2026-10-24: The Tempest
system  2026-10-25: Twelfth Night
system  The member asking is Grace.
user    Two seats for Sunday, please.