Ardian About Directory
Claude Code
Email me

Avagenc Zee

2026 — An AI agent that runs the lights, switches, and curtains of a Tuya smart home

01 A room it cannot see

Tuya is a cloud that many smart home brands build on, and it lets software run the lights and curtains in a home. Putting a language model in charge of that looks simple, until you remember that a language model is a program that only reads text and writes text. It has no way to see a room.

Lights and curtains change through physical switches and the Tuya app, outside any conversation the model is part of, so what it read an hour ago may no longer be true.

The model can also write a sentence without doing anything. A false "done, the curtains are closed" reads exactly like a true one, and the person reading it cannot tell which one they got.

02 What Zee is

Zee is a language model given a written instruction and four functions it may ask to run, called tools. The model still cannot touch a curtain. What it can do is answer with a request to run a tool, such as send_commands on this device with these values, instead of answering in words. The program around it runs the tool, hands the result back, and lets the model choose the next step, until the model finally answers in words. That loop of reading, acting, and reading the result is what makes Zee an agent and not a chatbot, and it is how a model that cannot see the room gets to check it.

The whole agent is one Go function built on Google's Agent Development Kit, ADK for short, the library that runs this loop. It takes the model, a client for Tuya's cloud, and an optional extra instruction, and returns an agent that has Zee's instruction and tools but no server, no users, and no saved conversations of its own.

//go:embed internal/description.txt
var description string

//go:embed internal/instruction.txt
var systemInstruction string

func New(cfg Config) (agent.Agent, error) {
tools, err := adktuya.Tools(cfg.TuyaClient)
if err != nil {
return nil, fmt.Errorf("zee: tuya tools: %w", err)
}

instruction := "[SYSTEM_INSTRUCTION]" + systemInstruction + "\n[/SYSTEM_INSTRUCTION]"
if cfg.AdditionalInstruction != "" {
instruction = "[SYSTEM_INSTRUCTION]" + systemInstruction + "\n\n" + cfg.AdditionalInstruction + "\n[/SYSTEM_INSTRUCTION]"
}

a, err := llmagent.New(llmagent.Config{
Name: "zee",
Model: cfg.Model,
Tools: tools,
Description: description,
Instruction: instruction,
})
if err != nil {
return nil, fmt.Errorf("zee: agent: %w", err)
}
return a, nil
}

03 Data points

To run a tool on a device, the model has to use the values the device understands. A Tuya device is never told "open" or "turn off". It is given data points, each one a code and a value. A light has a data point called switch that is true or false, and a curtain has one called percent_control that takes a number from 0 to 100.

That number runs the opposite way from what most people would guess. 0 is fully open and 100 is fully closed, so a model left to guess could close the curtains it was asked to open. Zee's instruction, written in Indonesian, states it outright. Translated, it reads "Lights, data point switch (true = ON, false = OFF). Curtains and blinds, data point percent_control (0 to 100), 0 = fully open, 100 = fully closed."

Anything the instruction does not name, Zee can read from the device itself, since every device lists its data points and their current values.

04 Three rules for acting

Knowing the values does not make the model use them, since it can still answer from what it has already read. So the same instruction gives three rules about acting.

Always verify. "I must not assume a device's state from the conversation history. Physical switches and the Tuya app can change the state at any time. I have to call a tool to make sure." The conversation may say Zee closed the curtains while someone has pulled them open by hand since. That is the curtain in the chat, where Zee reads percent_control 0 and says they are fully open.

Mandatory execution. "Every control or status check MUST trigger a tool call. Skipping the action is forbidden." A model can write "done, the lights are off" without doing anything, and that sentence reads exactly like the true one.

Silent execution. "Do NOT output any text before or while calling a tool, no 'one moment, I'm processing', no 'I'll turn it off first'. Call the needed tool straight away, then give EXACTLY ONE final response after seeing the result." This rule came in on 22 June 2026 to stop turns where Zee answered with two messages instead of one.

05 Four tools

Those rules only work if the tools can reach the home. They come from agentkit, my library that hands outside services to ADK agents, and its Tuya part gives Zee four of them. get_account checks that a Tuya account is linked to the person, list_devices returns every device with its id, kind, data points, and the names given to each button of a multi-button switch, device_status reads one device, and send_commands sets data points on one device.

Each tool acts as the person who is talking. ADK hands every tool the user ID of the run, and the Tuya client looks up which Tuya account is linked to that person, so Zee only ever reaches that person's home. A device that is not on the account is refused before any command is sent to it.

When a call fails, ADK passes the error to the model as text. agentkit rewrites the two failures a person can fix into sentences the model can pass on, and every other error reaches the model unchanged. The first of those sentences is what list_devices returns in the second conversation of the chat, and Zee tells the person in its own words.

func forAgent(err error) error {
switch {
case errors.Is(err, tuya.ErrAccountNotLinked):
return errors.New("the human hasn't linked their Tuya account yet — they need to link it before I can see or control any device")
case errors.Is(err, tuya.ErrDeviceNotOwned):
return errors.New("that device isn't on the human's Tuya account — I can only act on their own devices; double-check the id with list_devices")
default:
return err
}
}

06 Use in Avagenc

Zee began in February 2026 as a web service of its own. On 21 June 2026 it became a Go module with no server, and the next day its instruction dropped what tied it to Ava, the lead Avagenc agent, and to a group chat.

Avagenc Chat is the only product importing the module today. It hands in the model, the Tuya client, and specialist.Instruction() through AdditionalInstruction. That instruction says Zee sits in a group of one human and the Avagenc agents, and that it goes straight to the result without repeating the request.

Zee also carries a short description for whoever hands it work. It says Zee has no scheduling ability, so a task for the future must never go to it, not even a few seconds ahead. Avagenc Ava is the agent who keeps those tasks.

zeeAgent, err := zee.New(zee.Config{Model: agentModel, TuyaClient: tuyaAppClient, AdditionalInstruction: specialist.Instruction()})

07 Linking an account

One step in Avagenc is not code at all. Tuya only reaches a home once the person's Tuya app account is linked to the cloud project, the account a product uses to call Tuya. Offering that link needs partner status with Tuya, so the Avagenc team links each account by hand.

Until that happens, Zee can only say what it says in the second conversation of the chat, that the account is not linked.

Avagenc Zee

Reads the device before it believes the conversation, and speaks once, after Tuya has answered