Agent builder
An agent is not code and not a deployment. It is a folder in the user's own Drive, under the assistant's app folder:
AppData/<assistant folder>/agents/<Agent name>/
agent.md who the agent is and its standing instructions
agent.yaml what a run does, what starts it, how often it may run
skills/ skills specific to this agent (optional)
state/ written by runs, never by you
runs/ written by runs, never by you
The harness mirrors that folder into a workspace of its own, beside the
user's other workspaces, within about fifteen seconds of a change. The
user's chat can open the workspace and talk to the agent; when agent.yaml
declares a trigger, the harness also starts runs unattended and each run is
a session in that workspace, where the user reads it like any other.
Use this skill when the user asks for an assistant that does something on its own ("triage my inbox every morning", "every Friday, list what I committed to this week", "when mail arrives from the board, draft an acknowledgement"), or when they ask to change an agent they already have.
1. Agree on the agent in the conversation first
Ask, briefly, and only what the template below cannot default:
- Name. One plain phrase, it becomes the workspace title: "Inbox triage", "Weekly commitments". No slashes, no leading dot, sixty characters at most.
- What a run does, in one or two sentences. This becomes
prompt. - What starts it. Exactly one of:
every: 2h(a Go duration:30m,2h,24h),at: "0 17 * * FRI"(a cron schedule, five fields or@daily,@hourly; read in UTC, so convert the time of day they name and tell them you did), oron: mail.changes(a message arrived in the connected mailbox). A definition naming two is refused. - What it needs. The resources its runs will use, by kind, from what
list_accessreports:mail.mailboxfor the mailbox,storagefor Drive. This is not written in the folder; it is what you ask consent for in section 3. Do not ask for what the run will not use. - How careful. Defaults are right for almost everyone:
debounce: 2m(a burst of arrivals becomes one run),min_interval: 10m(never more often than this, whatever arrives, and one run at a time).
Read back the agreed definition in prose before writing anything. Keep it to what was agreed; do not invent behaviour the user did not ask for.
2. Write the folder with the harness's own write_agent tool
Call write_agent with name, agent_md and agent_yaml. The harness
writes agents/<name>/agent.md and agent.yaml into the assistant's folder
in the user's Drive (AppData/<label>/agents/<name>/) and mirrors the
folder back as a workspace. Do not write these files into this workspace,
and do not look for Drive tools to do it: Drive's own tools here are
read-only, and the harness does the writing. list_agents shows what the
user already has; writing an existing name updates that agent.
Write exactly these two files. A skill of the agent's own (skills/) and
the state/ and runs/ folders are not yours to create: the user adds a
skill in Drive if they want one, the harness makes the other two.
agent.md:
# <Agent name>
You are <the user's first name>'s <what the agent is for>, running in the
Privasys Harness on their behalf.
## What you do
<The agreed job, in the user's words, as instructions.>
## How you work
- Follow the `<skill name>` skill for the steps and the taxonomy.
- Write only through the tools you were granted; never send mail.
- Write nothing outside this workspace's `state/` and `runs/` folders.
- Never change `agent.md`, `agent.yaml` or `skills/` from a run. If the
definition should change, say so in the run's summary and stop.
- Finish every run with a short summary: what arrived, what you did, what
needs the user.
agent.yaml:
prompt: <one or two sentences: what a run does>
trigger:
on: mail.changes # or: every: 2h, or: at: "0 17 * * FRI" (one of the three)
debounce: 2m
min_interval: 10m
paused: false
If write_agent says the user's Drive folder for this assistant is not
connected, go to list_access: the storage resource is what it needs,
and request_access asks the user's device for it.
3. Ask for each consent, here, now, in the right order
For every resource kind agreed in section 1, look at list_access, then
verify with the service itself before trusting approved: the list is
what the user's device recorded, and the service is the authority (the
user may have withdrawn the service's folder in Drive since). Make one
harmless call to the service's tool that reads its state (for
mail.mailbox, the mail tool account); if it answers, the resource is
connected and approved.
There is exactly one way a service gets what it needs from the user:
request_access, after you asked the user. Their device does the rest
on one screen: it verifies the service, and if the service needs something
first (for mail.mailbox, their address and an app password), it asks for
it there and sends it straight to the service. Nothing of that enters this
conversation and you never see it. Never ask for a password yourself, and
never look for a tool of the service that would connect, link or set it up:
there is none you may call. Tell the user, before calling, that their
device will ask them to approve and may ask for the service's details on
that same screen.
So:
- The resource is not
approved: ask the user, then callrequest_access. - The list says
approved(orwithdrawn) and the service refuses: the device's record is stale. Ask the user, then callrequest_accesswithask_again: true. A refusal that saysneeds_holderorcredential_neededmeans exactly this, whatever else it says: the user has to act on their device, andrequest_accessis what puts it there. - The resource is
declined: respect that unless the user says otherwise, and only then ask again withask_again: true.
An agent whose consent is missing will not poll a service that refuses it: the harness asks the user's device once, the same way, and waits for the approval; better to settle it now, while they are here.
Do not ask for the assistant's spend consent here; that is settled at sign-in and is not per agent.
4. Tell the user what happens next, precisely
- Within about a minute the folder is mirrored and the agent exists.
- With
every, the first run starts within a minute and then on the interval; withat, at the next schedule time (UTC); withon: mail.changes, at the next arrival. - The workspace named after the agent appears in the sidebar at its first
run, with that run as its first session. Sessions are named
<Agent name> <date time>. - To change the agent, they edit the two files in Drive, or ask you in
the chat; either way the next run follows the edit. To stop it, set
paused: true. The harness sets that itself, on every agent of theirs, if their account cannot pay for a run; it says so in the agent'sruns/paused.md, and they ask you here to unpause. - To remove it, delete the folder in Drive.
What an agent is NOT
An agent is two files in the user's Drive. It is not a program. Never
write a script, a daemon, a scheduler, a poller or any code for it, never
create files in this workspace for it, and never run anything in the
background yourself: the harness reads agent.yaml and runs the agent on
its trigger, in a session of its own, with the same tools you have. If you
find yourself checking for Python, Node or an HTTP API, or writing files
into this workspace, stop: you have left this skill. Go back to section 2
and call write_agent.
Rules you do not bend
- Definition changes happen in conversation, never inside a scheduled run. A run that thinks its definition is wrong reports that and stops.
- One file per fact.
agent.mdis prose for the model,agent.yamlis data for the harness; do not duplicate the prompt in both. - No secrets in the folder. A mailbox password is typed into the connector's own form, never into an agent file and never into this conversation.
- No new capabilities by prose. An agent cannot grant itself a resource
by mentioning it in
agent.md; only the user's consent, given throughrequest_access, counts. - Do not create an agent for something the chat can do on the spot. A one-off question is a question, not an agent.
Example: the inbox agent
User: "Triage my inbox as mail comes in, drafts only, and give me a digest."
Agreed: name Inbox triage; runs on mail.changes; prompt "Triage what
arrived since the last run following the inbox-triage skill; label, draft
only where a reply is owed, and end with a digest."; it needs
mail.mailbox; defaults for the rest. Then section 3 for mail.mailbox
(request_access, with ask_again: true only if the service refuses an access the list calls approved), and the four sentences of section 4.