diff --git a/redirects.mjs b/redirects.mjs index 122fb271..45a161a5 100644 --- a/redirects.mjs +++ b/redirects.mjs @@ -32,6 +32,8 @@ const explicitRedirects = { // The Pi Durable page merged into the Pi page when `pi()` became the Pi // Durable agent. '/agents/docs/pi-durable': '/agents/docs/pi/', + // The Instructions page became the System Prompt page. + '/agents/docs/instructions': '/agents/docs/system-prompt/', // Product marketing pages folded into each product's docs overview. '/actors': '/actors/docs/', '/workflows': '/workflows/docs/', diff --git a/scripts/check-seo.ts b/scripts/check-seo.ts index 4e906255..d9e4d3a8 100644 --- a/scripts/check-seo.ts +++ b/scripts/check-seo.ts @@ -69,6 +69,8 @@ const ALLOWED_HISTORICAL_REDIRECT_LINKS = new Set([ "/actors/self-host/", "/agentos/docs/resource-limits/", "/agentos/docs/security-model/", + // The agents Instructions page became System Prompt. + "/agents/docs/instructions/", "/cloud/byoc/", "/cloud/byoc/architecture/", "/cloud/byoc/quickstart/", diff --git a/vendor/agents/docs/content/docs/extensions.mdx b/vendor/agents/docs/content/docs/extensions.mdx index e9afa7f3..885402f7 100644 --- a/vendor/agents/docs/content/docs/extensions.mdx +++ b/vendor/agents/docs/content/docs/extensions.mdx @@ -42,7 +42,7 @@ An extension is a named bundle of what an agent runs with: tools, prompt section | Field | Adds | See | | --- | --- | --- | | `tools` | Functions the model can call. | [Custom Tools](/agents/docs/custom-tools) | -| `sections` | Parts of the system prompt. | [Instructions](/agents/docs/instructions) | +| `sections` | Parts of the system prompt. | [System Prompt](/agents/docs/system-prompt) | | `hooks` | Code that runs around model requests, tool calls, and compaction. | [Hooks](#hooks) | | `wraps` | Decorators for a tool or section, by name. | [Wrap a tool](#wrap-a-tool) | | `tasks` | Multi-step work that resumes after a crash. | [Tools and tasks](/agents/docs/pi#tools-and-tasks) | diff --git a/vendor/agents/docs/content/docs/index.mdx b/vendor/agents/docs/content/docs/index.mdx index 33b5eb4b..ce484398 100644 --- a/vendor/agents/docs/content/docs/index.mdx +++ b/vendor/agents/docs/content/docs/index.mdx @@ -102,8 +102,11 @@ Each agent on Rivet is one [Rivet Actor](/actors/docs). You define the agent onc ## Concepts - - Set an agent's system prompt and project rules, and add context to a single prompt. + + Build an agent's system prompt from sections, and load AGENTS.md from the workspace. + + + Give an agent instructions for specific tasks that it loads only when a task needs them. Let your users run agents on their own Claude or ChatGPT subscription. diff --git a/vendor/agents/docs/content/docs/pi.mdx b/vendor/agents/docs/content/docs/pi.mdx index fdf9ccd0..edacb813 100644 --- a/vendor/agents/docs/content/docs/pi.mdx +++ b/vendor/agents/docs/content/docs/pi.mdx @@ -183,8 +183,7 @@ See [Architecture](/agents/docs/architecture). ## Known limitations -- **Skills aren't supported yet.** Put the skill files in the workspace and add a prompt section that tells the model where they are, so it reads the right one before acting. See [Instructions](/agents/docs/instructions). -- **Pi doesn't load `AGENTS.md` files.** Set `instructions` or add a prompt section. See [Instructions](/agents/docs/instructions). +- **No automatic `AGENTS.md` or skill discovery.** Load them with prompt sections. See [System Prompt](/agents/docs/system-prompt) and [Skills](/agents/docs/skills). - **No shell without a sandbox.** `bash` needs a [sandbox](/agents/docs/sandboxes). - **Plain tool calls aren't rerun after a crash.** Use a task or `replay: "safe"`. - **A tool that ignores its abort signal blocks sleep.** An upgrade then waits up to `sleepGracePeriod` for it to finish. diff --git a/vendor/agents/docs/content/docs/skills.mdx b/vendor/agents/docs/content/docs/skills.mdx new file mode 100644 index 00000000..c6f8da21 --- /dev/null +++ b/vendor/agents/docs/content/docs/skills.mdx @@ -0,0 +1,41 @@ +--- +title: "Skills" +description: "Give an agent instructions for specific tasks that it loads only when a task needs them." +--- + +A skill is a `SKILL.md` file with a `name` and a `description`, followed by instructions for one kind of task. The model sees every skill's name and description in the [system prompt](/agents/docs/system-prompt) and loads a skill's instructions only when a task matches, so an agent can have many skills without filling its context. + +## Write a skill + +Put each skill in its own folder as `skills//SKILL.md`: + +```md +--- +name: commit-style +description: How to write commit messages in this project. +--- + +Write commit messages in the imperative mood and keep the subject under 50 characters. +``` + +The model decides when to use a skill from its `description` alone, so say which tasks it covers. + +## Load skills + +Pi doesn't discover skill files on its own. This extension lists them in a prompt section and loads them with a tool: + + + + + + +- **The `skills` section** lists every skill's name and description. Like every [section](/agents/docs/system-prompt#sections-for-every-conversation), it renders before each model call. +- **The `skill` tool** returns a skill's instructions. It sets `replay: "safe"`, so a call cut off by a crash runs again. +- **Skills ship with your worker.** They're read when the worker starts, and a deploy updates them. They work with or without a sandbox, and the agent can't change them. +- **Skills can differ per agent.** Look them up from your database in the section and the tool instead of from files, and every agent gets its own list. + +## Skills in the workspace + +Skill files that are already in the workspace, such as `.agents/skills/` in a repository cloned into the [sandbox](/agents/docs/sandboxes), need no tool. Add a section that says where they are, and the model reads a skill's `SKILL.md` with Pi's `read` tool before it starts the task. + +**Next:** [Extensions](/agents/docs/extensions), how to bundle tools, sections, and hooks and choose them per conversation. diff --git a/vendor/agents/docs/content/docs/instructions.mdx b/vendor/agents/docs/content/docs/system-prompt.mdx similarity index 59% rename from vendor/agents/docs/content/docs/instructions.mdx rename to vendor/agents/docs/content/docs/system-prompt.mdx index 803ad5a5..b23c8830 100644 --- a/vendor/agents/docs/content/docs/instructions.mdx +++ b/vendor/agents/docs/content/docs/system-prompt.mdx @@ -1,6 +1,6 @@ --- -title: "Instructions" -description: "Set an agent's system prompt and project rules, and add context to a single prompt." +title: "System Prompt" +description: "Build an agent's system prompt from sections, load AGENTS.md from the workspace, and add instructions to one conversation or context to one prompt." --- An agent's system prompt is built from prompt sections. Your extensions add sections that every conversation gets, and a conversation can add its own `instructions` after them. The agent renders the system prompt again before every model call. @@ -32,25 +32,39 @@ An agent's system prompt is built from prompt sections. Your extensions add sect ## Sections for every conversation -Add sections with an extension. Sections come in the order the extensions are installed. +Add sections with an extension. A section is a key and a function that returns its text: - + -The model gets the `payments-team` sections as tagged blocks: +The model gets each section as a block tagged with its key: ```text You review pull requests for the payments team. Be brief and cite file paths. - + +Today is Mon Oct 12 2026. + + Run `npm test` before proposing a fix. Never edit files in `migrations/`. - - -The `skills/` folder has one folder per skill. Read a skill's SKILL.md before you start a task it covers. - + ``` -The agent runs each section's function again before every model call. A section can return text that changes, such as the current date or a value from your database. +- **Sections render before every model call.** A section can return text that changes, such as the current date, a file, or a value from your database. +- **Order follows the conversation's extensions,** which is install order by default. See [Extensions](/agents/docs/extensions). +- **Return `undefined` to leave a section out.** Pass `{ tag: false }` as the third argument of `section()` to send the text without the tags. +- **A section that throws keeps its previous text.** The model keeps what it already had, and the error is reported. +- **`wrapSection`** changes a section from another extension by its key. See [Wrap a tool](/agents/docs/extensions#wrap-a-tool). + +## AGENTS.md + +Pi doesn't read `AGENTS.md` on its own. Load it with a section, as `agents_md` does above. The section gets the conversation's workspace as `input.env`, the same files Pi's `read` tool sees: the [sandbox](/agents/docs/sandboxes), or the files in the Actor's database when there is no sandbox. An edit to `AGENTS.md` reaches the model on its next call. + +With a sandbox, reading the file connects the sandbox on the first model call instead of the first tool call. + +## How changes reach the model + +The agent records a change to the system prompt in the conversation, at the point where it changed. After a restart, and in a fork, the conversation has exactly the system prompt the model saw at each point. On models that accept system prompt changes in the middle of a conversation, only the change is sent, so the provider's prompt cache stays valid. ## Instructions for one conversation @@ -60,11 +74,6 @@ Call `conversation.configure(id, { instructions })` to give one conversation its Put context for a single turn in the prompt itself. The `prompt` action takes a string, or an array of text and image blocks. Each image is `{ type: "image", data, mimeType }`, with the image base64-encoded in `data`. The system prompt stays the same. - - -## AGENTS.md and skills - -Pi doesn't support `AGENTS.md` files or skills yet. Use these workarounds: + -- **Project rules:** put the rules of your `AGENTS.md` in a section, as `project-rules` does above, or in a conversation's `instructions`. -- **Skills:** put the skill files in the agent's workspace, such as the [sandbox](/agents/docs/sandboxes) or the files in the Actor's database. Then add a section that tells the model where the skills are and to read one before it acts, as `skills` does above. +**Next:** [Skills](/agents/docs/skills), how to give an agent instructions it loads only when a task needs them. diff --git a/vendor/agents/docs/sidebar.json b/vendor/agents/docs/sidebar.json index f83ac9e8..e980f209 100644 --- a/vendor/agents/docs/sidebar.json +++ b/vendor/agents/docs/sidebar.json @@ -66,8 +66,12 @@ "icon": "faRobot", "pages": [ { - "title": "Instructions", - "href": "/agents/docs/instructions" + "title": "System Prompt", + "href": "/agents/docs/system-prompt" + }, + { + "title": "Skills", + "href": "/agents/docs/skills" }, { "title": "Extensions", diff --git a/vendor/agents/examples/docs/skills/server.ts b/vendor/agents/examples/docs/skills/server.ts new file mode 100644 index 00000000..f2ee71c2 --- /dev/null +++ b/vendor/agents/examples/docs/skills/server.ts @@ -0,0 +1,20 @@ +import { createRegistry } from "@earendil-works/pi-durable"; +import { CodingTools } from "@earendil-works/pi-durable/tools"; +import { pi } from "@rivet-dev/pi"; +import { e2bProvider } from "@rivet-dev/sandbox-adapter/e2b"; +import { setup } from "rivetkit"; +import { Skills } from "./skills"; + +const extensions = createRegistry(); +extensions.install(CodingTools); +extensions.install(Skills); + +const agent = pi({ + model: "anthropic/claude-opus-5-5", + registry: extensions, + sandbox: e2bProvider(), +}); + +export const registry = setup({ use: { agent } }); + +registry.start(); diff --git a/vendor/agents/examples/docs/skills/skills.ts b/vendor/agents/examples/docs/skills/skills.ts new file mode 100644 index 00000000..365ec04e --- /dev/null +++ b/vendor/agents/examples/docs/skills/skills.ts @@ -0,0 +1,42 @@ +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { Type } from "@earendil-works/pi-ai"; +import { parseFrontmatter } from "@earendil-works/pi-coding-agent"; +import { defineExtension, defineTool, section } from "@earendil-works/pi-durable"; + +// One folder per skill, next to this file: skills//SKILL.md. +const root = join(import.meta.dirname, "skills"); +const skills = new Map(); +for (const folder of await readdir(root, { withFileTypes: true })) { + if (!folder.isDirectory()) continue; + const file = await readFile(join(root, folder.name, "SKILL.md"), "utf8"); + const { frontmatter, body } = parseFrontmatter<{ name?: string; description?: string }>(file); + skills.set(frontmatter.name ?? folder.name, { description: frontmatter.description ?? "", body }); +} + +const loadSkill = defineTool({ + name: "skill", + description: "Load a skill's instructions by name.", + parameters: Type.Object({ name: Type.String() }), + // Reading a skill changes nothing, so running it twice is harmless. + replay: "safe", + execute: async ({ name }) => { + const skill = skills.get(name); + if (!skill) throw new Error(`No skill named ${name}. Skills: ${[...skills.keys()].join(", ")}.`); + return { content: [{ type: "text", text: skill.body }] }; + }, +}); + +export const Skills = defineExtension({ + name: "skills", + tools: [loadSkill], + sections: [ + // The model sees every skill's name and description. It loads a skill's instructions only when it needs them. + section("skills", () => + [ + "Skills hold instructions for specific tasks. Before you start a task that matches a skill's description, call the `skill` tool with its name and follow what it returns.", + ...[...skills].map(([name, { description }]) => `- ${name}: ${description}`), + ].join("\n"), + ), + ], +}); diff --git a/vendor/agents/examples/docs/skills/skills/commit-style/SKILL.md b/vendor/agents/examples/docs/skills/skills/commit-style/SKILL.md new file mode 100644 index 00000000..4c09a86a --- /dev/null +++ b/vendor/agents/examples/docs/skills/skills/commit-style/SKILL.md @@ -0,0 +1,6 @@ +--- +name: commit-style +description: How to write commit messages in this project. +--- + +Write commit messages in the imperative mood and keep the subject under 50 characters. diff --git a/vendor/agents/examples/docs/instructions/client.ts b/vendor/agents/examples/docs/system-prompt/client.ts similarity index 100% rename from vendor/agents/examples/docs/instructions/client.ts rename to vendor/agents/examples/docs/system-prompt/client.ts diff --git a/vendor/agents/examples/docs/instructions/server.ts b/vendor/agents/examples/docs/system-prompt/server.ts similarity index 51% rename from vendor/agents/examples/docs/instructions/server.ts rename to vendor/agents/examples/docs/system-prompt/server.ts index 11b3b29a..f12d40a2 100644 --- a/vendor/agents/examples/docs/instructions/server.ts +++ b/vendor/agents/examples/docs/system-prompt/server.ts @@ -1,3 +1,4 @@ +import { posix } from "node:path"; import { createRegistry, defineExtension, section } from "@earendil-works/pi-durable"; import { CodingTools } from "@earendil-works/pi-durable/tools"; import { pi } from "@rivet-dev/pi"; @@ -11,9 +12,18 @@ extensions.install( name: "payments-team", sections: [ section("role", () => "You review pull requests for the payments team. Be brief and cite file paths."), - section("project-rules", () => "Run `npm test` before proposing a fix. Never edit files in `migrations/`."), - // The skill files are in the sandbox. This section tells the model where to find them. - section("skills", () => "The `skills/` folder has one folder per skill. Read a skill's SKILL.md before you start a task it covers."), + // Rendered again before every model call, so the date stays current. + section("date", () => `Today is ${new Date().toDateString()}.`), + // Read from the conversation's workspace, so an edit to AGENTS.md applies from the next model call. + section("agents_md", async (input, context) => { + if (!input.env) return undefined; + const file = await input.env.readTextFile(posix.join(input.env.cwd, "AGENTS.md"), context); + if (file.ok) return file.value; + // No AGENTS.md: leave the section out. + if (file.error.code === "not_found") return undefined; + // Any other failure keeps the text the model already has. + throw file.error; + }), ], }), );