Documentation

Everything here is done in the browser. Follow the five numbered steps and you will have a working assistant that checks with you before it does anything you would rather approve first.

Run it

You need Docker. That is the whole list — and if you do not have it, the command below offers to install it.

macOS and Linux

curl -fsSL https://heddled.com/install.sh | sh

Windows, in PowerShell:

irm https://heddled.com/install.ps1 | iex

The script checks for Docker, downloads Heddled into a heddled folder, starts it, and waits until it answers before telling you it is ready. It asks before installing anything and will not write over a folder that is already there. Reading a script before piping it to a shell is a good habit: both are plain files, so curl -fsSL https://heddled.com/install.sh on its own prints it.

Or do it by hand

Nothing above is magic. It comes down to:

git clone https://github.com/heddled/heddled
cd heddled
docker compose up

Set HEDDLED_PORT first if 5005 is taken. The install script takes the same setting.

That is the console, the API, the record of everything, and the background worker, together. Open http://localhost:5005.

The first time, it asks you to claim it — pick a username and password. That account is the administrator. Do this immediately if the machine is reachable by anyone else: an unclaimed Heddled lets whoever arrives first become the administrator.

You do not need an account with an AI provider to follow this guide. The built-in stand-in works offline and is enough to see a whole conversation, an approval and a full record.

1 · Give it something to do

An assistant on its own can only talk. To be useful it needs actions — one thing each: look a customer up, check a list, send a message. Actions are shared, so the same one can be given to several assistants.

Go to Tools → New tool and pick the kind you need. Most need no code at all.

The new tool screen, offering kinds: look something up, call an
                    API, send an email, post to a webhook, always answer the same, or
                    write it in Python.
Six kinds. Only the last one involves writing anything.

Pick Look something up and you get a form: a name, what it does, what it needs to be told, and the list to search. The description matters more than it looks — it is what the assistant reads when deciding whether this is the right action for the question in front of it.

The form for a look-something-up action: name, description, what it
                    needs to know, and the data to search.
Name it after what it does. The assistant chooses actions by their names and descriptions.

Check it works before anything depends on it

Every action has a Test tab that runs it on its own — no assistant, no AI, no conversation. Put in some arguments, press Run, and see exactly what comes back.

An action's test panel: a box of arguments, a Run button, and the
                    result printed underneath.
If it fails here, it will fail for the assistant too — and this is a much easier place to find out.

2 · Make the assistant

Agents → New agent. Five short steps:

  1. A name — lowercase, no spaces. It can be changed later, and everything pointing at it follows.
  2. How it should behave — plain language, as if briefing a new colleague. Say what it is for, what to always do, and what never to do.
  3. What it may use — tick the actions from step 1.
  4. What needs your approval — only offers what you ticked above.
  5. Which model — leave the stand-in for now if you have no key.
The new agent form, numbered one to five: name, how it should
                    behave, what it is allowed to do, what to check first, and model.
Write the instructions as instructions, not as a description. "Always look an invoice up before quoting an amount" beats "helpful billing assistant".

Afterwards, everything is on the assistant's own page. What it can do shows what is chosen as removable labels, with the full list folded behind a search — useful once you have more than a handful.

The what-it-can-do panel: chosen actions as removable labels above
                    a folded, searchable list of everything available.
Click the × on a label to take an action away.

3 · Try it

Press Try it and say something. The panel beside the chat fills in as it works — the question arriving, each action it runs, and the reply going back.

The Try it screen: a chat box on the left with suggested openers,
                    and a live panel of steps on the right.
Nothing here is customer-facing. It is your own scratch conversation, and it is saved under Activity like any other.

If the assistant ignores an action you expected it to use, the usual cause is the action's description, not the assistant's instructions. Make the description say plainly what question it answers.

4 · Read what it did

Open any conversation under Activity. What happened is the plain-words account: who asked, what was looked up, what came back, what was said. That is usually all you need.

A conversation written out in plain words, with a timestamp on
                    each step.
Old conversations read the same way months later.

Every step beside it has the underlying record — the exact context the model was sent, every argument, every result, timings and token counts. Press j and k to move between steps, Enter to expand one.

The same conversation as a numbered list of events with the
                    selected step's full payload beside it.
This is the same conversation, not a different log. Both views read the same record.

Two other things live on this page: Step in adds a note to a running conversation as though the assistant had been told it, and Save as a test keeps the conversation to replay later — see Save a test.

5 · Make it check with you

On the assistant's page, under What it checks with you first, add a rule: choose the action, tick ask me first, and optionally cap what it may spend in a day or hide sensitive fields from the record.

The approvals panel: existing rules listed, and a form to add one
                    with ask-me-first, a daily spend cap, and fields to hide.
Use this for anything that spends money, reaches a customer, or cannot be undone.

Now the assistant stops before that action and waits. The platform holds the turn — this is not an instruction the model could talk itself out of.

A paused conversation: the assistant wants to issue a refund of 249
                    euros for a duplicate payment, with Approve and Refuse buttons.
Approve it and the conversation carries on from exactly that point — minutes or days later, and across a restart.

The request also goes wherever you set up under Settings → Where things go: a webhook, Slack, or just the console. Whoever approves gets a link that needs no account.

Let it start itself

Under When it acts on its own, three things can begin a conversation with nobody typing:

Set off byYou give itGood for
A time of day or weekHow often, and at what timeThe 08:00 summary of what is overdue
A file arriving in a folderThe folder to watchA scanner, an export, a colleague saving a file
An email arriving in a mailboxMail server, sign-in, folderAn invoices@ or support@ address
The triggers panel: existing triggers listed with what sets each one
                    off, and a form to add another.
Each trigger says what it watches and when it last looked.

Heddled remembers where it got to, so nothing is handled twice — even across a restart. Mail server details are kept under Settings rather than in the assistant's file, because that file is meant to be shareable and a password is not.

Let other systems in

Every assistant has its own addresses, listed on its page, ready to copy. Whether each is open depends on what the assistant is mounted on.

A table of ways in: a webhook address, an MCP address, and the chat
                    window, each saying whether it is open.
Click an address to copy it.

A program signs in as Bearer <username>:<password>, or with an integration key for a system that has no account. Whatever arrives this way obeys the same rules — your approval gates still apply.

curl -X POST http://localhost:5005/api/agents/billing_support/webhook \
  -H "authorization: Bearer alex:your-password" \
  -H "content-type: application/json" \
  -d '{"text": "Is invoice F-2231 paid?", "sync": true}'

It works in the other direction too: an assistant can use any MCP server as a source of actions, and can be given another assistant as an action so a specialist handles part of the work.

Save a test

Have a conversation that goes the way you want, then press Save as a test on it. Later, after you have changed the assistant, replay it and see what changed.

The Tests screen: saved conversations on the left, a picker to run
                    them, and past results underneath.
An assistant with nothing saved cannot be picked — the button says so rather than running something empty.

A replay sends the same messages to the current version, with actions in playback: each gives back exactly what it gave back the first time. Running your tests can never send a real email or move real money.

Heddled reports whether the assistant used the same actions with equivalent arguments, and whether the answer still passes its checks — exact match, contains, a pattern, or judged by a model.

Publish a version

Every time you change an assistant it gets a new version, and every version is kept. Publish binds one of them to an environment, and that decides what actually runs:

EnvironmentRuns
devWhatever you are editing right now
staging, prodThe version published there — it keeps running while you edit
The Publish screen: each assistant with its latest edit and what is
                    running in dev, staging and prod.
"Running 4fe69731" means live is on an older version than the one you are editing. Click it to see what changed.

Publishing to prod asks for a passing test run on that exact version first. You can overrule it, but it will ask. On the assistant's page, Earlier versions lists every definition it has had — compare any of them with what you are editing now, or put one back.

Work arriving from outside belongs to dev until you say otherwise, so publishing changes nothing at first. Set default_env to prod under Settings once you are publishing deliberately — then editing an assistant stops changing what your live traffic does.

Add people

People is where accounts live. Three levels: an admin can change everything including settings and people, a member can build and run assistants, and a viewer can look but not change.

The People screen: everyone with an account, what each may do, and
                    a record of changes underneath.
Underneath is a record of who changed what — separate from what the assistants did, which is under Activity.

Somebody suspended keeps their record but cannot sign in. If you lock yourself out, heddled user add on the machine itself is the way back in.

Where it all lives

Each assistant is one file, and the console edits that same file. Neither is the "real" version — they are the same bytes, so you can review changes, keep them in version control, or edit by hand.

# agents/billing_support.yaml
name: billing_support
description: Invoice and payment questions for the finance team.
model: anthropic/claude-sonnet-4-6
instructions: ./billing_support.md   # or write them inline

adapters:
  channels: [webchat, webhook]
  tools:
    - lookup_invoice
    - issue_refund
    - agent:office_helper              # another assistant, as an action

triggers:
  - schedule: "0 8 * * 1-5"
    message: "Flag anything unpaid."

policies:
  - tool: issue_refund
    requires_approval: true
    budget: { max_eur_per_day: 500 }
  - tool: "*"
    redact: [iban, creditcard]     # kept out of the record

memory:
  session: auto                     # rolling summary, on by default

expose:
  mcp: true                        # other systems can call it

Actions live in tools/<name>/ — a manifest saying what goes in and out, and a handler if it needs one:

# tools/lookup_invoice/handler.py
def handle(args, ctx):
    invoice = db.find(args["invoice_number"])
    return {"status": invoice.status, "amount_eur": invoice.total}

Settings and secrets are kept separately, in the store rather than in files — those are the things you would not want to commit.

The Settings screen, grouped: models and their keys, where things
                    go, other systems, watching a mailbox, monitoring.
A key you type here is never handed back — the page shows it masked.

Models

An agent names its model as service/model. Each service has its own key, so you can have several configured at once.

Written asServiceKey
anthropic/claude-sonnet-4-6AnthropicANTHROPIC_API_KEY
openai/gpt-4oOpenAIOPENAI_API_KEY
deepseek/deepseek-chatDeepSeekDEEPSEEK_API_KEY
groq/llama-3.3-70b-versatileGroqGROQ_API_KEY
mistral/mistral-large-latestMistralMISTRAL_API_KEY
openrouter/…OpenRouterOPENROUTER_API_KEY
ollama/llama3.2Ollama, on your machinenone needed
vllm/your-modelYour own servernone needed
mock/echoBuilt-in stand-innone needed

Keys go under Settings or in the environment. Any service that speaks the OpenAI chat-completions API works — point <service>_base_url at a proxy or a gateway if you need to.

Command line

CommandWhat it does
heddled devConsole with the trace pane, opened on the Test tab
heddled serveConsole, API and worker together
heddled workerRun turns as a separate process
heddled chat <agent> "…"One scripted turn
heddled trace <session>Print a conversation, event by event
heddled new agent|tool|policyScaffold one; --from clones an existing one
heddled mv agent|tool <old> <new>Rename, following every reference
heddled rm agent|tool <name>Delete, refusing if something depends on it
heddled tool test <name>Run a tool on its own
heddled approve [id]List or resolve approvals
heddled eval run <agent>Replay saved conversations
heddled deploy <agent> <env>Publish a version
heddled user addAdd somebody, from the machine itself

Configuration

VariablePurpose
HEDDLED_ROOTProject root — agents, tools, data, var
HEDDLED_PORT / HEDDLED_HOSTWhere the console listens
HEDDLED_DEFAULT_ENVEnvironment for work arriving from outside
HEDDLED_HTTPSSet behind TLS so session cookies are marked secure
HEDDLED_KEEP_FULL_CONTEXT_DAYSHow long full model contexts are kept (default 90)
HEDDLED_WEB_ONLYServe HTTP only; run heddled worker separately
OTEL_EXPORTER_OTLP_ENDPOINTExport every turn to your own tracing stack

Anything set in Settings wins over the environment, so you can change most things without a restart.

Before you put it on a network: claim the console immediately — an unclaimed Heddled lets whoever finds it first make themselves administrator. Put it behind TLS and set HEDDLED_HTTPS=1.