Skip to content

Agent-Native Design Principles

When the primary consumer of your Clip is an LLM Agent, the design assumptions change fundamentally. This document derives Clip design principles from first principles.

DimensionHuman (CLI)Agent (function call)
OutputReads text, scans visuallyParses structured data, counts tokens
Cost bottleneckLatency (waiting)Tokens (input + output)
CompositionPipes (|), shell scriptsFunction calls, parallel dispatch
State modelSession, cwd, env varsStateless — every call is independent
ConcurrencySequential (one terminal)Parallel function calls
Discoveryman, --help, memoryManifest, schema, auto-discovery

The most important difference: tokens cost money and consume context window. A verbose response that helps a human wastes tokens for an Agent.

Every byte of response costs tokens. Design for minimal, decision-ready output.

List operations should return just enough to decide the next action:

// Good: index response — minimal, decision-ready
clip.command("list", {
handler: async () => ({
items: [
{ id: "t1", title: "Buy groceries", status: "pending" },
{ id: "t2", title: "Review PR #42", status: "done" },
],
total: 2,
}),
});
// Bad: returns full details in list
clip.command("list", {
handler: async () => ({
items: [
{
id: "t1", title: "Buy groceries", status: "pending",
description: "...", created_at: "...", updated_at: "...",
tags: [...], subtasks: [...], comments: [...]
},
// ...
],
}),
});

Detail operations return full information for a specific item:

clip.command("get", {
input: { id: { type: "string", required: true } },
handler: async ({ input }) => {
// Full details — only when the Agent needs them
return await db.getById(input.id);
},
});

This two-phase pattern (list → select → get) is the most token-efficient way to navigate data.

Every invocation must be self-contained. No sessions, no “current context,” no implicit state.

// Good: self-contained, ID-addressed
clip.command("get", {
input: { id: { type: "string", required: true } },
handler: async ({ input }) => db.getById(input.id),
});
// Bad: depends on previous "select" call
clip.command("get-current", {
handler: async () => db.getCurrent(), // What is "current"?
});

Why: Agents can call multiple tools in parallel. If your Clip depends on ordering or state from a previous call, parallel execution breaks.

Lists are parallel dispatch points. When an Agent gets a list of IDs, it can fetch details for all of them simultaneously:

Agent gets: [{ id: "a" }, { id: "b" }, { id: "c" }]
Agent dispatches in parallel:
get(id="a") ──►
get(id="b") ──► all at once
get(id="c") ──►

Clips must be self-describing. The manifest should tell the Agent everything it needs to know to use the Clip correctly.

Guidelines for command design:

  • Verbs for commands: search, create, list, get, update, delete
  • Nouns for subcommand groups: message send, message list
  • No camelCase: get-details not getDetails
  • Merge when semantics overlap: if search and find do the same thing, keep only one

A Clip runs across multiple protocols: CLI, IPC, HTTP, MCP, Connect-RPC. Design for the canonical form (space-separated commands), and let each protocol adapter handle translation.

ProtocolCommand format
CLIpinix invoke todo list
IPC / MCP{ command: "list" }
HTTPGET /todo/list

Your command handler doesn’t need to know which protocol is calling it — @pinixai/core handles the translation.

Phase 1: List (index) → Agent gets IDs + minimal info
Phase 2: Get (detail) → Agent fetches full details for selected items

Responses should include everything needed for the next action:

// Good: includes IDs for follow-up actions
{
items: [
{ id: "t1", title: "...", status: "pending" }
// ^^^ Agent can use this to call complete(id="t1")
]
}
// Bad: Agent has to make another call to get actionable IDs
{
items: [
{ title: "...", status: "pending" }
// No ID — Agent can't do anything with this
]
}

Each write command does one thing:

// Good: separate commands
clip.command("update-title", { ... });
clip.command("update-status", { ... });
clip.command("add-tag", { ... });
// Bad: one command tries to do everything
clip.command("update", {
input: {
title: { type: "string" }, // which fields
status: { type: "string" }, // are being
tags: { type: "array" }, // updated?
},
});

After a write, return the updated entity so the Agent doesn’t need a separate fetch:

clip.command("complete", {
input: { id: { type: "string", required: true } },
handler: async ({ input }) => {
const task = await db.complete(input.id);
return task; // Return the updated task, not just { success: true }
},
});

Before publishing your Clip, verify:

  • List operations return index-level data (IDs, titles, status)
  • Detail operations are ID-addressed and self-contained
  • Every response includes IDs for follow-up actions
  • No implicit state between commands
  • Write operations return the affected entity
  • Command count is under 10 (or uses command groups)
  • All input parameters have types and descriptions
  • Commands work correctly via CLI, IPC, and MCP