Talk to Control
Languages: English · 中文
The problem
A user controls something — a document, a dashboard, a set of records — by typing natural-language commands. Each turn, the model:
- Understands what the user means.
- Picks an action from a fixed set.
- Executes the action against the current domain object.
- Replies with what it did and what state things are in now.
The shape
User input → Agent (with actions) → action call(s) → updated state → reply
▲
│
Session (multi-turn history)This is fundamentally a conversational agent with actions. No TriggerFlow needed unless you have multi-step processes per turn.
Walkthrough
from agently import Agently
agent = (
Agently.create_agent()
.role("You control a shopping cart. Use the available actions to make changes.", always=True)
.info({"format": "After each action, briefly confirm what changed."}, always=True)
)
# A simple in-memory cart for the demo
cart = {"items": [], "total": 0.0}
@agent.action_func
def add_item(name: str, price: float, quantity: int = 1):
"""Add a product to the cart."""
cart["items"].append({"name": name, "price": price, "quantity": quantity})
cart["total"] += price * quantity
return cart
@agent.action_func
def remove_item(name: str):
"""Remove a product from the cart."""
cart["items"] = [i for i in cart["items"] if i["name"] != name]
cart["total"] = sum(i["price"] * i["quantity"] for i in cart["items"])
return cart
@agent.action_func
def show_cart():
"""Return the current cart state."""
return cart
agent.use_actions([add_item, remove_item, show_cart])
# Enable a session so multi-turn context is bounded
agent.activate_session(session_id="cart-demo")
# Conversation loop
while True:
user_text = input("> ")
if not user_text.strip():
break
reply = agent.input(user_text).start()
print(reply)Why these choices
@agent.action_funcfor each operation, not a single "do anything" tool — small, well-named actions let the model pick correctly. A monolithic tool forces the model to encode parameters in JSON inside a string.role(always=True)for behavior,info(always=True)for formatting — both are stored on the agent and included in every request that agent runs, so they count toward each request's prompt.activate_session()instead of manual chat history management — for chat-style interactions, Session maintains full history and the current context window. Register a custom resize handler when you need summarization. See Session Memory.- Cart kept as module state for the demo — in real code this would be a database, with the action functions doing reads/writes. The shape doesn't change.
- No structured output schema — the agent's reply is for a human. Don't force structure unless something else consumes it programmatically.
Variations
Stream the reply
For a UI, switch to streaming so the user sees the response as it's generated:
gen = agent.input(user_text).get_generator(type="delta")
for delta in gen:
print(delta, end="", flush=True)See Model Response for streaming options.
Add a structured side-channel
If your UI needs to know which action ran (to highlight a row, animate, etc.), hold the request-scoped turn and pass its prompt to the action loop:
turn = agent.input(user_text)
records = agent.get_action_result(prompt=turn.prompt)
for r in records:
notify_ui(action=r.name, args=r.input, result=r.output)Multi-step per turn
If a single user message triggers a multi-step process (lookup → confirm → apply), promote the per-turn handling to a TriggerFlow. The conversation layer still lives in the agent; the flow runs inside one turn. See TriggerFlow Orchestration Playbook.
Cross-links
- Action Runtime —
@agent.action_funcanduse_actions - Session Memory —
activate_session(), windowing, custom memo - Async First — async equivalents of the loop above