Get started
Concepts
The seven words you need to navigate the rest of these docs without flinching.
4 min read
AgentGraft has a small vocabulary. Read this page once and the rest of the docs will make sense without you having to keep a glossary open. Every term here corresponds to either a database row, a decorator, or both — there are no abstract concepts you can't grep for.
#The mental model in one sentence
A Workspace owns one or more Grafts; each Graft is a single chat-widget integration; each Graft has its own Tools, Agents, and prompt content; the host app declares all of that in code via a named Profile and pushes it with one sync command.
#Workspace
Workspaces are the multi-tenant boundary. A user with a membership in workspace A cannot see grafts, conversations, or members of workspace B unless they're also a member there. Switching workspaces in the dashboard is a navigation action, not a login — your identity stays the same, only the scope changes.
#Graft
The metaphor is botanical. A graft is grown on a host (your Django app) and bears its fruit (the chat experience) on that host's surface. The hosted runtime provides the scaffolding but the integration belongs to your codebase.
#Profile
AGENTGRAFT['profiles'] settings dict that maps to one graft's credentials. Profile names are arbitrary strings — default, marketing, admin. Every decorator (@ag.tool, @ag.agent) takes an optional profile= keyword that pins it to a specific profile's registry.If your host app only has one chat experience, the default profile is the only one you need. Multi-profile setups become useful when you have, say, a marketing chat for anonymous visitors AND an in-app helper for authenticated users — same Python file, two completely separated tool registries, two separated grafts on the server.
#Tool
@ag.tool. Takes the resolved user object as its first argument; type hints on subsequent arguments are auto-converted to a JSON schema the LLM consumes. The function runs inside your host app's process — AgentGraft's runtime hits your webhook, the SDK dispatches to the matching function, the return value is JSON-serialised back to the LLM.@ag.tool
def get_dataset(user: User, dataset_id: int) -> dict:
"""Get one dataset's metadata by id."""
ds = Dataset.objects.get(pk=dataset_id, owner=user)
return {"id": ds.id, "name": ds.name, "rows": ds.row_count}
#Agent
@ag.agent. One agent per graft must be the entry point — that's the agent the orchestrator hands every new conversation to. Optionally, one other agent is private: it answers the widget's incognito chat, which is not saved.Agents are how you compose complex chat experiences without writing one giant system prompt. A typical setup has a small router agent that triages incoming messages, plus a handful of specialist agents (one for billing questions, one for data lookups, one for documentation search) that the router hands off to as needed.
#Policy
ag.policy("agentgraft/no-hallucination") and the sync command fetches the body from the Policy Shop and substitutes it into your graft's prompt at sync time. Policies cover common needs like 'never invent values', 'cite sources', 'use markdown for emphasis', 'redact secrets'.Policies are an optional convenience — you can write any text directly into the policies=[...] list on your graft. The Policy Shop just saves you from copy-pasting the same five rules into every project.
#User identity
@ag.user_loader. Whatever you return becomes the first argument to every tool function in that profile.AgentGraft never stores your users — it just passes their external ids around. The widget signs the user id with the graft's webhook secret on every request, AgentGraft's runtime verifies the signature, and your user_loader does the actual lookup against your own database.
#How they fit together
Read it bottom-up. A Tool is a Python function. A bunch of tools, plus an entry agent and maybe some specialists, plus a system prompt with policies, all decorated under one Profile, become one Graft's declarative config. That graft lives inside a Workspace. The host app pushes the config via agentgraft_sync, drops one widget tag, and the bubble appears.