Agents

Agents are alpha and not part of the frozen v1 contract. The agent kind, the ai.teild.dev vocabulary (llm, thread, message), and the /agents chat and call wire are all alpha and unfrozen at v1. They are marked alpha in the docs and in API discovery, and they may change, or be superseded, without counting as a v1 wire break. Build on the frozen core (entities, the API, functions, triggers) with confidence; treat agents as a preview.

An agent is a callable whose body is an LLM loop, run host-side. It has one identity and the same four ways in as a function: a trigger delivery (a callable reference whose type is agent), the call API, a sub-agent call, and chat. Its actor is agent.<name>.<group>, and its dispatch stamps the extension tier on its writes, exactly like a function's. A bundled agent obeys its bundle's lifecycle: while the bundle is disabled or uninstalled, every entry refuses with a guard error.

The agent schema kind is a first-class core kind, identity agent.core.teild.dev, like every other schema kind. Its runtime vocabulary, by contrast, lives in its own group.

The llm, thread, and message vocabulary is consolidated into a single ai.teild.dev group, so everything AI lives in one place. Their full type identities are llm.ai.teild.dev, thread.ai.teild.dev, and message.ai.teild.dev. There is no leftover agents.teild.dev group.

The manifest

data carries:

Because schema is entities, a parsed agent projects to a row the console lists and creates like any other, with first-class columns (name, group, description, prompt, llm, functions, subagents) mirroring the manifest and a definition json carrying the authoritative envelope.

Tools

A tools: entry is a bare string, one of the two built-ins or a function identity, or a {callable, name, description} alias that recolors this agent's prompt context without changing the function's canonical description. Tool names are unique per agent.

Applied effects land in their own transaction under the agent's actor, each row recording the triggering seq as its cause. Creation and deletion still flow through the reviewed request: propose emits the request, and accepting it is the write.

Sub-agents, budgets, and the emit ceiling

Sub-agents are child invocations with their own budgets and a child thread carrying the parent edge. agentDepth is 0 at the root and increments per hop, checked against the calling agent's budgets.depth (hard-capped at 3), so the default chain refuses the hop that would run at depth 4, as a tool error the caller sees.

The emit ceiling rides every hop. A child's effective emit is its own declared emit intersected with the caller's effective emit, enforced against function-tool effects and propose alike. An empty-emit parent delegating to a write-capable child yields a child that writes nothing: no chain of children can ever write a type its narrowest ancestor could not. Accepting a proposed request is authorized as the transitive write too, so a function or agent that drives an accept must have the concrete written type in its effective emit, or the accept refuses. An owner's acceptance stays unbounded.

Threads, messages, and cost

The conversation state is the run: there is no separate run entity. A thread is written as the loop runs under the agent's actor, carrying agent, llm, mode, status (running then ok/overbudget/error), agentDepth, the tallies (turns, toolCalls, the token counts, costUSD), and startedAt/finishedAt. A message carries role, content, turn, and the tool-call audit, plus the required thread edge.

Cost rolls up onto the root thread: every loop on a chain adds to one shared tally, so the root thread's numbers include every descendant while a child thread carries only its own. Pricing is data on the llm row, never a table in code. The loop terminates on the final tool-free reply, any budget, or its deadline; over-budget is a settled outcome (thread overbudget with a reason), never a park. A loop error (the LLM transport failing) rides the ordinary retries and parks, each attempt a fresh thread but not fresh idempotency keys, so an effectful tool that honors keys never double-fires across a retry.

LLM rows

llm rows are pure data over the host-native OpenAI-compatible transport: name, provider, baseURL, model, defaults (temperature), pricing, and an optional secret apiKey. A row that declares its own baseURL must declare its own apiKey, or it refuses to resolve: the host gateway key never travels to a row-defined endpoint. Three well-known rows, cheap, mid, and strong, seed at tenant open create-only, each pinning a concrete gateway model id and its pricing pair, so an owner's re-tiering or deliberate delete stands. Exotic providers wrap as function tools, never as the transport.

Calling an agent

POST …/agents/{name}/call with {"input": …} runs the loop once: the input becomes the first user message, and the answer carries reply, thread, status, effects with its effectsByAction breakdown, turns, toolCalls, and the token and costUSD tallies. POST …/agents/{name}/chat with {"thread"?, "message"} opens or continues a thread and streams the loop: application/x-ndjson, one JSON object per line keyed by kind (thread first, delta carrying text per streamed token, toolStarted and toolFinished around each dispatch, one done carrying the settled result under result). A loop that fails after the 200 status line has already gone out terminates the stream with an error event instead, the same choice the changelog makes, so a failure is never a done with no result. A continued thread replays its prose history and keeps one running tally on the thread row; one active turn per thread is enforced by a lease, so a second concurrent turn is refused as a conflict.

Next: the extensions catalog, what ships today.