BESSER-Skills / references
AGENTS.md9 starsChanged 5 months agoApache-2.0View on GitHub
Agents extend state machines with NLP — intents, entities, and platform adapters. The BAFGenerator produces a runnable agent script plus config. For plain state machines (no NLP), see state-machines.md. Agent is a StateMachine — all state machine concepts (states, events, conditions, bodies, fallback bodies, config properties) apply. See state-machines.md for those building blocks. Register at least one platform before generating: Both methods are idempotent — calling them twice returns None the second time without duplicating the platform. WebSocket platform —…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository4 other files it gives its agents.
- besser-devSkill
- besser-generatorsSkill
- besser-troubleshootingSkill
- besser-userSkill
The file
We split this agents.md into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 159
Use intent.parameter() to attach named entity slots to an intent.
- Very specific: line 278
Use an LLM in a body via LLMReply:
- Very specific: line 323
Use in a body via RAGReply:
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent (Chatbot) Modeling Reference
Agents extend state machines with NLP — intents, entities, and platform
adapters. The BAFGenerator produces a runnable agent script plus config.
For plain state machines (no NLP), see state-machines.md.
Table of contents
- Imports
- Creating an agent
- Platforms
- States
- Intents and entities
- Transitions
- State behavior / actions
- LLM integration
- RAG (retrieval-augmented generation)
- Database replies
- Intent classifier configuration
- ReasoningState
- Tools, skills, and workspaces
- Session object in body code
- Validation
- Generation
- Complete example
Imports
from besser.BUML.metamodel.state_machine.agent import (
Agent,
# Actions
AgentReply, LLMReply, RAGReply, DBReply,
# Platforms
WebSocketPlatform, TelegramPlatform,
# Intents & entities
Intent, IntentParameter,
BaseEntity, BaseEntityImpl, CustomEntity, EntityEntry,
# Intent classifier configuration
SimpleIntentClassifierConfiguration,
LLMIntentClassifierConfiguration, LLMSuite,
# Agentic primitives
ReasoningState, Tool, Skill, Workspace,
# File handling
File,
)
from besser.BUML.metamodel.state_machine.state_machine import Body, CustomCodeActionCreating an agent
agent = Agent(name="helpdesk")Agent is a StateMachine — all state machine concepts (states, events,
conditions, bodies, fallback bodies, config properties) apply. See
state-machines.md for those building blocks.
Platforms
Register at least one platform before generating:
# WebSocket — pairs with the React chat widget from WebAppGenerator
ws = agent.use_websocket_platform()
# Telegram
tg = agent.use_telegram_platform()Both methods are idempotent — calling them twice returns None the second
time without duplicating the platform.
WebSocket platform — rich reply methods (call from body code via session):
ws.reply_options(session, ["Option A", "Option B", "Option C"])
ws.reply_file(session, file_obj)
ws.reply_dataframe(session, df)
ws.reply_plotly(session, fig)
ws.reply_location(session, latitude=48.85, longitude=2.35)Telegram platform — rich reply methods:
tg.reply_file(session, file_obj, message="Here is the file")
tg.reply_image(session, file_obj, message="Here is the image")
tg.reply_location(session, latitude=48.85, longitude=2.35)States
initial = agent.new_state(name="initial", initial=True)
greeting = agent.new_state(name="greeting")
fallback = agent.new_state(name="fallback")The first state must be initial=True — the metamodel raises ValueError
otherwise. State names must not collide with intent names (case-insensitive
— validation catches this).
To override the intent classifier for a specific state, pass ic_config=:
expert_state = agent.new_state(
name="expert",
ic_config=SimpleIntentClassifierConfiguration(num_epochs=100),
)A state can also be registered as a global entry point for an intent — the agent jumps to it from any state when that intent matches:
help_state.set_global(help_intent) # from any state, "help" goes hereIntents and entities
Intents
hello = agent.new_intent(
name="hello_intent",
training_sentences=["Hi", "Hello", "Hey there", "Good morning"],
description="User greets the agent", # optional; helps LLM classifiers
)Base entities (built-in types)
number_entity = agent.new_entity(name=BaseEntityImpl.number, base_entity=True)
datetime_entity = agent.new_entity(name=BaseEntityImpl.datetime, base_entity=True)
any_entity = agent.new_entity(name=BaseEntityImpl.any, base_entity=True)BaseEntityImpl values: number, datetime, any.
Custom entities (enumerated values + synonyms)
size_entity = agent.new_entity(
name="size",
description="Pizza size",
entries={
"small": ["S", "petit"],
"medium": ["M"],
"large": ["L", "big"],
},
)Slot-filling — adding parameters to an intent
Use intent.parameter() to attach named entity slots to an intent. These
map fragments of the user's utterance to entity values at runtime.
order_pizza = agent.new_intent(
name="order_pizza",
training_sentences=["I want a pizza", "order me a pizza"],
)
order_pizza.parameter(name="size_param", fragment="size", entity=size_entity)
order_pizza.parameter(name="topping_param", fragment="topping", entity=topping_entity)At runtime, session.predicted_intent holds the matched intent; read
extracted slot values via session.predicted_intent.get_parameter(name):
def show_order(session):
pred = session.predicted_intent
size_match = pred.get_parameter("size_param")
if size_match:
session.reply(f"Size: {size_match.value}")Transitions
Intent-based transitions
initial.when_intent_matched(hello).go_to(greeting)
initial.when_no_intent_matched().go_to(fallback)when_intent_matched raises ValueError if the intent is not registered on
the agent or if the same intent is added twice to the same state.
Session variable transitions
Transition when a stored session variable satisfies a predicate:
import operator
routing.when_variable_matches_operation(
var_name="mode",
operation=operator.eq,
target="expert",
).go_to(expert_state)
routing.when_variable_matches_operation(
var_name="score",
operation=operator.ge,
target=80,
).go_to(pass_state)File-received transitions
# Accept any file type
initial.when_file_received().go_to(file_handler)
# Accept only specific types
initial.when_file_received(allowed_types=["pdf", "txt"]).go_to(doc_handler)
initial.when_file_received(allowed_types="image").go_to(image_handler)Auto (unconditional) transition
greeting.go_to(initial) # immediately moves to initial after greeting body runsState behavior / actions
greeting.set_body(Body(name="greet", actions=[
AgentReply(message="Hello! How can I help you today?"),
]))Actions available in a body:
| Action | Effect |
AgentReply(message=...) | Send a static text reply |
LLMReply(prompt=..., llm_name=...) | Generate a reply via a registered LLM |
RAGReply(rag_db_name=..., prompt=...) | Reply using a registered RAG pipeline |
DBReply(...) | Query a database and reply with the result |
CustomCodeAction(source="...") | Run arbitrary Python (advanced) |
Both prompt= and llm_name= on LLMReply are optional — omitting
llm_name uses the agent's default_llm_name.
LLM integration
Register one or more LLMs on the agent. The first one registered automatically becomes the default.
agent.new_llm(
name="gpt4o",
provider="openai",
parameters={"model_name": "gpt-4o"},
num_previous_messages=5, # how many prior turns to include
global_context="You are a helpful support agent.",
)Supported provider values: "openai", "huggingface", "huggingface_api",
"replicate".
Change the default after registering multiple LLMs:
agent.new_llm(name="fast_model", provider="openai", parameters={"model_name": "gpt-4o-mini"})
agent.new_llm(name="smart_model", provider="openai", parameters={"model_name": "gpt-4o"})
agent.set_default_llm("smart_model")Use an LLM in a body via LLMReply:
support.set_body(Body(name="answer", actions=[
LLMReply(
prompt="Answer the user's support question concisely.",
llm_name="smart_model", # omit to use default
)
]))Validation catches references to unregistered LLM names (in LLMReply,
DBReply, RAGReply, ReasoningState, and default_llm_name).
RAG (retrieval-augmented generation)
RAG replies retrieve documents from a vector store and pass them to an LLM.
from besser.BUML.metamodel.state_machine.agent import (
RAGVectorStore, RAGTextSplitter, RAGReply,
)
vector_store = RAGVectorStore(
embedding_provider="openai",
embedding_parameters={"model": "text-embedding-3-small"},
persist_directory="./rag_db",
)
splitter = RAGTextSplitter(
splitter_type="recursive",
chunk_size=500,
chunk_overlap=50,
)
rag = agent.new_rag(
name="company_docs",
vector_store=vector_store,
splitter=splitter,
llm_name="gpt4o", # must be a registered LLM name
k=4, # number of retrieved chunks
num_previous_messages=0,
)Use in a body via RAGReply:
knowledge_state.set_body(Body(name="rag_answer", actions=[
RAGReply(
rag_db_name="company_docs",
prompt="Answer based only on the retrieved context.",
)
]))Database replies
DBReply queries a database and sends the result as a reply.
query_state.set_body(Body(name="db_query", actions=[
DBReply(
db_selection_type="default", # "default" | "custom"
db_custom_name=None, # required when db_selection_type="custom"
db_query_mode="llm_query", # "llm_query" | "sql"
db_operation="select", # "any" | "select" | "insert" | "update" | "delete"
db_sql_query=None, # raw SQL string; used when db_query_mode="sql"
llm_name="gpt4o", # required when db_query_mode="llm_query"
)
]))Intent classifier configuration
The intent classifier maps free-text input to intents. Set a default for the whole agent, or override per state.
SimpleIntentClassifierConfiguration (local neural network)
ic = SimpleIntentClassifierConfiguration(
num_words=1000,
num_epochs=300,
embedding_dim=128,
input_max_num_tokens=15,
discard_oov_sentences=True,
check_exact_prediction_match=True,
activation_last_layer='sigmoid',
activation_hidden_layers='tanh',
lr=0.001,
)
agent.default_ic_config = icLLMIntentClassifierConfiguration (LLM-based)
llm_ic = LLMIntentClassifierConfiguration(
llm_suite=LLMSuite.openai,
parameters={"model_name": "gpt-4o"},
use_intent_descriptions=True, # use intent description= field
use_training_sentences=False,
use_entity_descriptions=True,
use_entity_synonyms=True,
llm_name="gpt4o", # must be a registered LLM name
)
agent.default_ic_config = llm_icLLMSuite values: openai, huggingface, huggingface_inference_api,
replicate.
Override for a single state:
specialized = agent.new_state(
name="specialized",
ic_config=SimpleIntentClassifierConfiguration(num_epochs=100),
)ReasoningState
A ReasoningState is an autonomous LLM loop that can use tools, skills, and
workspaces to complete multi-step tasks — no hand-written body needed.
reasoning = agent.new_reasoning_state(
name="reasoning",
llm="gpt4o", # registered LLM name
initial=False,
max_steps=8, # maximum tool-call iterations
enable_task_planning=True, # let the LLM plan sub-tasks
stream_steps=True, # stream intermediate steps to the user
system_prompt="You are a helpful company support agent.",
fallback_message="I could not complete the task. Please try again.",
)ReasoningState does not accept set_body() or set_fallback_body() —
configure behavior via the constructor parameters instead.
Transition to and from a reasoning state like any other state:
routing.go_to(reasoning)
reasoning.go_to(initial)Tools, skills, and workspaces
These primitives give a ReasoningState callable functions, domain
knowledge, and file-system access.
Tools — callable Python functions
agent.new_tool(
name="search_docs",
description="Search company docs for an answer. Returns a string.",
code="""def search_docs(query: str) -> str:
# implementation here
return f"Results for: {query}"
""",
)name must be a valid Python identifier and must match the function name in
code. Validation raises an error if code is empty or contains no def.
Skills — text knowledge injected into the LLM context
agent.new_skill(
name="support_guidelines",
content="Always be polite. Escalate billing issues to a human agent.",
description="Customer support behavioral guidelines",
)Workspaces — file-system paths the agent can browse
agent.new_workspace(
name="knowledge_base",
path="./docs",
description="Company knowledge base with product manuals.",
writable=False,
max_read_bytes=200_000,
)Validation warns when description is empty (the LLM may not know to use
the workspace) and errors when path is empty or max_read_bytes <= 0.
Session object in body code
CustomCodeAction and callable= bodies receive a session object
(AgentSession). Key attributes and methods:
# Standard (inherited from Session)
session.get("key") # read a per-user variable
session.set("key", value) # write a per-user variable
session.reply("message") # send a text reply to the user
# Agent-specific
session.message # the raw text the user sent
session.predicted_intent # IntentClassifierPrediction or None
session.file # File object if a file was received
session.chat_history # typed list[tuple[str, int]] in the metamodelReading a matched intent's extracted parameter values:
def handle_order(session):
pred = session.predicted_intent
size = pred.get_parameter("size_param")
if size:
session.reply(f"Ordered size: {size.value}")Validation
agent.validate() raises ValueError by default. Pass
raise_exception=False to inspect safely:
result = agent.validate(raise_exception=False)
print("valid:", result["success"])
for w in result["warnings"]:
print("warning:", w)
for e in result["errors"]:
print("error:", e)What it checks:
- State/intent name collisions (error) — a state and an intent share the same name (case-insensitive)
- Unregistered intent in transition (error) —
when_intent_matchedreferences an intent not added to the agent - Unregistered LLM references (error) —
LLMReply,DBReply,RAGReply,ReasoningState, ordefault_llm_namenames an LLM that was never registered vianew_llm() - Empty tool code / missing def (error) —
Tool.codeis blank or has no top-leveldef - Invalid tool name (error) — tool name is not a valid Python identifier
- Empty skill content (error) —
Skill.contentis blank - Empty workspace path (error) —
Workspace.pathis blank - Non-positive max_read_bytes (error) —
Workspace.max_read_bytes <= 0 - Missing workspace description (warning) — LLM may not know to use it
- Missing tool description (warning) — LLM may not pick it up reliably
Generation
from besser.generators.agents import BAFGenerator
gen = BAFGenerator(model=agent, output_dir="./agent_output")
gen.generate()Outputs:
{agent_name}.py— the agent scriptconfig.yaml— runtime configurationreadme.txt— quick start instructions- optional RAG subdirectories if the agent uses retrieval
See the besser-generators skill for BAFGenerator options:
generation_mode (FULL / PERSONALIZED_ONLY / CODE_ONLY), config_path,
openai_api_key for LLM-driven personalization.
Complete example
from besser.BUML.metamodel.state_machine.agent import (
Agent, AgentReply, LLMReply,
WebSocketPlatform,
SimpleIntentClassifierConfiguration,
BaseEntityImpl,
)
from besser.BUML.metamodel.state_machine.state_machine import Body, CustomCodeAction
agent = Agent(name="helpdesk")
agent.use_websocket_platform()
# Default intent classifier
agent.default_ic_config = SimpleIntentClassifierConfiguration(num_epochs=200)
# Entities
product_entity = agent.new_entity(
name="product",
description="Product name",
entries={"laptop": ["notebook", "computer"], "phone": ["mobile", "cell"]},
)
# Intents
hello = agent.new_intent(
name="hello_intent",
training_sentences=["Hi", "Hello", "Hey there", "Good morning"],
)
buy = agent.new_intent(
name="buy_intent",
training_sentences=["I want to buy", "purchase", "order"],
)
buy.parameter(name="product_param", fragment="product", entity=product_entity)
# States
initial = agent.new_state(name="initial", initial=True)
greeting = agent.new_state(name="greeting")
buying = agent.new_state(name="buying")
fallback = agent.new_state(name="fallback")
# Transitions
initial.when_intent_matched(hello).go_to(greeting)
initial.when_intent_matched(buy).go_to(buying)
initial.when_no_intent_matched().go_to(fallback)
# Bodies
greeting.set_body(Body(name="greet", actions=[
AgentReply(message="Hello! How can I help you?"),
]))
def show_product(session):
pred = session.predicted_intent
p = pred.get_parameter("product_param")
session.reply(f"Adding {p.value if p else 'item'} to your cart!")
buying.set_body(Body(name="show_product", callable=show_product))
fallback.set_body(Body(name="fallback_body", actions=[
AgentReply(message="Sorry, I didn't understand. Can you rephrase?"),
]))
# Return to initial after each turn
greeting.go_to(initial)
buying.go_to(initial)
fallback.go_to(initial)
result = agent.validate(raise_exception=False)
assert result["success"], result["errors"]
from besser.generators.agents import BAFGenerator
gen = BAFGenerator(model=agent, output_dir="./agent_output")
gen.generate()Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

