C&C Docs
Log in
Platform services

$ai

$ai runs an agent inside a _Space. The space's folder is the agent's workspace, and the files under .ai/ in it are the agent: its prompt and model, its tools and its skills. There is nothing to register: an app writes files, then calls the agent.

The docs talk to a stub model, so every turn is predictable: plain text comes back as echo: …, and a line like !tool my_books {} makes the model call a tool. A session belongs to whoever is asking, so if you are not signed in your first message makes you a guest account.

Talk to the agent

// In a browser handler (or a server controller, or a service):
const agent = $ai.agent('demo-chat', { space, agent: 'assistant' })
const off = agent.on('event', (event) => this.events.push(event))
const result = await agent.say(this.message)   // { turn, text, ... }
off()
this.reply = result.text

The agent is its files

Your first message builds this workspace with ordinary $data and $file calls. A tool runs as the person in the session, so my_books lists the books you may read: the seeded ones and your own. Access to the space is access to the agent; there is no second permission system.

// The workspace: a _Space whose root folder holds the agent's files.
workspace/
  .ai/agents/assistant.json     the agent
  .ai/tools/my_books.js        a tool
  .ai/skills/next-read/SKILL.md  a skill
  notes.md                     a file its read_file tool can see

// .ai/agents/assistant.json
{
  "prompt": "You are the C&C docs assistant. Answer briefly.",
  "model": {
    "provider": "llama.cpp",
    "id": "stub/script"
  },
  "tools": [
    "my_books"
  ],
  "skills": [
    "next-read"
  ]
}

// .ai/tools/my_books.js
// A tool runs as the person talking to the agent, so $data here
// sees exactly the books they may read: the seeded ones and their own.
export default {
  description: 'List the books the person talking to the agent may read',
  parameters: { type: 'object', properties: {} },
  async run() {
    const { rows } = await $data({
      _schema: 'DataBook',
      _select: ['title', 'author'],
      _order: [{ field: 'title', direction: 'asc' }],
    })
    return rows.map((book) => book.title + ' by ' + book.author)
  },
}

The session remembers

agent.history() is the conversation as the platform keeps it, durable across reloads. Reload this page and the session picks up where it left off.

Nothing said yet.