Skip to main content

Your First Agent

After running cast init, you have a working agent. Let’s understand its structure and how to customize it.

Agent Structure

Every Castari agent has this structure:
my-agent/
├── castari.json      # Agent configuration
├── package.json      # Dependencies
├── tsconfig.json     # TypeScript config
├── src/
│   └── index.ts      # Agent entry point
├── CLAUDE.md         # Agent instructions
└── README.md         # Documentation

castari.json

The agent configuration file:
{
  "name": "my-agent",
  "version": "0.1.0",
  "entrypoint": "src/index.ts",
  "runtime": "node"
}
FieldDescription
nameDisplay name for your agent
versionSemantic version
entrypointPath to main file
runtimeRuntime environment (node)

CLAUDE.md

This file contains instructions for Claude. It’s like a system prompt that shapes your agent’s behavior:
# My Agent

You are a helpful coding assistant.

## Guidelines

- Be concise and direct
- Always explain before making changes
- Ask for clarification when requirements are unclear

## Tools Available

- `read_file` - Read file contents
- `write_file` - Create or update files
- `bash` - Run shell commands
The better your CLAUDE.md, the better your agent performs. Be specific about capabilities, constraints, and expected behavior.

src/index.ts

The entry point defines your agent’s tools and handles invocations:
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// Define tools
const tools: Anthropic.Tool[] = [
  {
    name: "read_file",
    description: "Read the contents of a file",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string", description: "File path to read" }
      },
      required: ["path"]
    }
  },
  // ... more tools
];

// Handle tool calls
async function handleTool(name: string, input: any): Promise<string> {
  switch (name) {
    case "read_file":
      return fs.readFileSync(input.path, "utf-8");
    // ... handle other tools
  }
}

// Main agent loop
async function run(prompt: string) {
  // Agent implementation
}

// Read prompt from stdin, run agent, output to stdout
let prompt = "";
process.stdin.on("data", (chunk) => (prompt += chunk));
process.stdin.on("end", async () => {
  const response = await run(prompt);
  console.log(response);
});

Local Testing

Test your agent locally before deploying:
cd my-agent
echo "What files are here?" | npm run dev
This runs your agent with the prompt as input, just like cast invoke does remotely.

Customizing Your Agent

Add a New Tool

  1. Define the tool schema in tools array
  2. Add the handler in handleTool function
// 1. Define the tool
{
  name: "search_web",
  description: "Search the web for information",
  input_schema: {
    type: "object",
    properties: {
      query: { type: "string", description: "Search query" }
    },
    required: ["query"]
  }
}

// 2. Handle the tool
case "search_web":
  return await searchWeb(input.query);

Change Agent Behavior

Edit CLAUDE.md to change how your agent thinks and acts:
# Research Agent

You are a research assistant that finds and synthesizes information.

## Guidelines

- Always cite sources
- Present multiple perspectives
- Summarize findings at the end

Add Dependencies

npm install axios  # Example: HTTP client
Then import and use in your agent:
import axios from 'axios';

// In your tool handler
const response = await axios.get('https://api.example.com/data');

Redeploying

After making changes, redeploy:
cast deploy
This uploads your changes and creates a fresh sandbox.
Redeploying destroys the previous sandbox. Any files or state in the sandbox are lost.

Next Steps