A friendly robot

Home | Docs for Agents | GitHub | Sponsor

ClankTalk Docs

ClankTalk is under construction!

Overview

ClankTalk is a scripting language designed mainly for agents to write and for humans to read. Humans write a little bit of it, defining tools and policies for agents in the ClankTalk runtime, but that's pretty much it.

ClankTalk is an xTalk language, which reads like English. As we learn every time hyperscript is posted to HackerNews or Reddit, many people dislike writing code in this style. But we think, if you have an open mind, you can agree that it at least reads well.

And, for a language intended to be written by agents and read by humans... it's just about perfect.

The ClankTalk runtime is designed to be a replacement for giving your agent unrestricted shell access.

If you think about it, it's pretty crazy that we do this: shell can run any program, with all of your rights. It can read your secrets: .env, ~/.ssh, cloud credentials. It can send anything to any host. It can change or delete any file, also outside the project. Pipes, quotes and $(...) make its commands hard to read, and harder to audit.

bump.sh
for f in ./config/*.json; do
  v=$(jq -r '.version' "$f")
  if [ "$v" -lt 2 ]; then
    jq '.version = 2' "$f" > "$f.tmp" \
      && mv "$f.tmp" "$f"
  fi
done
bump.talk
for file in ./config/*.json
  set config to the file's text as json
  if config's version is less than 2
    set config's version to 2
    write config as json to the file
  end
end

ClankTalk replaces this, let's just say it, digital anarchy, with a much more controlled runtime environment for your agents to do work in, providing:

The ClankTalk environment isn't perfect, of course, but it gives you a lot more control and observability when compared with most agent runtimes today.

Install

ClankTalk runs as an MCP server with one tool, clank_talk. There is also a pi package available for it.

The nice thing about this architecture is that it can work with any agent and in any harness: all you have to do is enable the MCP server and then turn off all (or most) of the other tools available, and the agent will start using it.

Pick Your Harness

Claude Code

Add the server:

 
$ claude mcp add clank-talk -- npx -y -p clank-talk@0.1 clank-talk-mcp

Then start Claude Code with ClankTalk as its only tool:

 
$ claude --tools "" --allowedTools mcp__clank-talk

If you are using ClankTalk, we recommend that you put the following directions in your AGENTS.md

 
## Tools

Use the clank_talk tool for all work: reading, editing, search, git and tests.
Do not use a shell or other file tools.  If other tools are available warn the user.
If ClankTalk cannot do a task, stop and tell the human what is missing.

Your AGENTS.talk

As we mentioned, you, the reader, will not likely write very much ClankTalk (you will mostly be reading the ClankTalk your agent wrote.) However, there is one area where you will be asked to write some code: the AGENTS.talk file.

The AGENTS.talk File

An AGENTS.talk file lives in the root of your project and is loaded by the ClankTalk environment when it starts up and defines:

Here is a simple AGENTS.talk file:

 
# AGENTS.talk
allow env PATH, HOME

# Runs the full test suite.
tool run_tests
  run 'npm test' in a sandbox with local network
end

This AGENTS.talk file passes the PATH and HOME environment variables on to the programs that its tools run, and then defines a custom tool for running the tests in the project.

The in a sandbox part runs npm test in an OS sandbox. In the sandbox, the program can write only to your project (not to .git, .talk files or hidden files), it can read only system folders, its install folder and tool caches, and it has no network.

The with local network part then gives the program localhost, so tests that start a local server still work.

The program still cannot get to the internet.

Agents can then execute the run_tests tool and read its output.

Finding AGENTS.talk

ClankTalk resolves the project root to look for AGENTS.talk in the following order:

  1. --root /path/to/project, if you start the MCP server with it.
  2. The environment variable CLANK_TALK_ROOT=/path/to/project.
  3. The folder where your agent starts.

Note that agents cannot modify any .talk files in your project, so they are a protected space for you to write permissions and tools for them.

A Tool for Running A Single Test

Running the entire test suite is great, but sometimes you only want to run a single test. Or, at least, you only want your agent to run a single test.

Let's add another tool to run a single test:

AGENTS.talk
# Runs one test file.
tool run_test_file with file
  run 'node' with '--test', file in a sandbox
end

Your agent can can call this new tool with a file:

 
set tests to run_test_file @/test/app.test.js
if tests's code is not 0
  print find 'FAIL' in tests's output
end

The @/test/app.test.js path is a project relative path (don't worry, your agent will figure it out.)

Note that an agent can never execute run directly, only via the tools that you give it in an AGENT.talk file. The agent can't use shell and the execution passes in only the variables in allow env.

This gives you significant control over what an agent is and is not allowed to do in your project.

Dealing With Hanging Tests

Now let's go back and improve the run_tests tools.

Let's say that it is prone to hanging at times and we don't want our tool to wait forever when it does.

We can add within 2 minutes to the end of our run and it will kill a hung test.

AGENTS.talk
# Runs the full test suite.
tool run_tests
  run 'npm test' in a sandbox with local network within 2 minutes
end

Agent can set shorter limit on a tool call: run_tests within 2 minutes as well.

Accessing A Cache Outside the Project

By default, ClankTalk runtime file access is sandboxed to the current project directory and subdirectories.

This is not a fool-proof sandbox in the strict sense, but a best-effort "prevent agents from messing with stuff" mechanism.

Of course, sometimes you might need to give tools access to a directory (e.g. a playwright cache).

You can do this in your AGENTS.talk tool definitions like so:

AGENTS.talk
# Runs the full test suite.
tool run_tests
  run 'npm test'
    in a sandbox with local network, reads of '~/.cache/ms-playwright'
    within 10 minutes
end

Fixing the CI Config

Another CatTalk default is that agents are not allowed to change hidden files.

This prevents rogue agents from changing dangerous files like .git/hooks/pre-commit, a script that git runs on your machine at your next commit.

Sometimes, though, you want to allow an agent to modify hidden files like your CI config in .github/.

To do so, you can use the allow changes syntax in your AGENTS.talk file:

AGENTS.talk
allow changes to @/.github/

Note that ClankTalk never allows agent changes to .talk files and also bans all reading and writing of .env, .env.* and .envrs files in any folders.

The Dev Server

Next, let's give our agent the ability to start a dev server for us by executing an NPM command, npm run dev

This is not a synchronous task, instead you want to be able to launch it and then continue working.

For something like that, you can use start

AGENTS.talk
allow fetch from http://localhost:5173/

# Starts the dev server.
tool dev_server
  start 'npm run dev' as 'dev' in a sandbox with local network
end

Note that we allow fetch from http://localhost:5173 explicitly: we don't want to give agents the ability to just spam our machine looking at ports, now do we? By default, fetches will be disallowed.

Your agent can then start it, and, if it wants, wait for it to come up:

 
dev_server
wait until the status of fetch http://localhost:5173/ as response
  is 200 within 15 seconds
print the output of process 'dev'

The agent can then stop the process with stop:

 
stop process 'dev'

Installing Packages

OK, let's add another tool to, sigh, install NPM packages.

npm install needs the wider network obviously, and also needs access to npm's cache directory.

To give it access to that we would write:

AGENTS.talk
# Installs the dependencies.
tool npm_install
  run 'npm install' in a sandbox with network, writes to '~/.npm'
end

We give the npm_install tool the minimal set of permissions it needs.

Calling the GitHub API

Sometimes you need secrets in a project. Of course, secrets are dangerous because they can leak.

ClankTalk uses the .env convention for secrets, and refuses to let scripts read or write .env files.

Secrets are resolved in the following way:

An empty value counts as no value. ClankTalk reads the files on each script run, so changes are picked up immediately.

The files use the usual dotenv form: KEY=value lines, # comments, an optional export, and quoted values.

Scripts cannot read .env, .env.* or .envrc files in any folder, and git diff and git show hide their contents.

ClankTalk loads only the secrets that an allow secret line specifies in your AGENTS.talk file:

AGENTS.talk
allow fetch from 'api.github.com'
allow secret GITHUB_TOKEN for fetch to 'api.github.com'

With this line, your agent can then use the token when fetching from Github.

However, it cannot see its value.

 
fetch https://api.github.com/user as json
  with headers: {
    Authorization: 'Bearer ' + secret GITHUB_TOKEN
  }

Publishing to npm

OK, now let's say we want our agent to publish our package to npm.

To publish, npm needs a token in its environment. We don't want the agent to ever see that token, so we give it to the tool rather than to the agent:

AGENTS.talk
allow secret NPM_TOKEN for run

# Publishes the package.
tool publish
  run 'npm publish' using secret NPM_TOKEN in a sandbox with network
end

The using secret NPM_TOKEN bit hands the token to npm as an environment variable. If the token ever shows up in the output, ClankTalk replaces it with <secret NPM_TOKEN>.

Now, a word of warning: a program with a secret and the network can send that secret anywhere it likes. And npm publish runs scripts from package.json, a file your agent can happily edit. So be careful about handing secrets to programs whose code your agent can change!

ClankTalk is here to help, but it's not omniscient!

Hooks

Let's say you want a global rule that every program your agent runs must be in a sandbox.

You could write that down in your AGENTS.md and hope for the best but, agents being agents, sooner or later one will forget.

What you really want is a way to verify on every run that the run is in a sandbox.

That's what hooks are for: they let you hook into before and after runtime calls to, well, pretty much anything, and potentially stop what's going on.

Here is a hook that rejects any run that isn't in a sandbox:

AGENTS.talk
before run
  if the run's sandbox is null
    reject 'Use a sandbox'
  end
end

A before hook runs before every step of its kind. If the hook calls reject, the step doesn't happen, and the script gets your message back as an error.

You can hook into pretty much anything an agent does:

Step Runs before
read a script reads a file
write a script writes a file
append a script adds text to the end of a file
replace a script replaces text in a file
put a script puts lines into a file
remove a script removes lines from a file
move a script moves a file
delete a script deletes a file
undo a script puts files back with undo
change any file change at all: all of the above except read
run a tool runs a program
start a tool starts a background program
stop a script stops a background process
git any git command
fetch a fetch from a URL
<tool name> a call to one of your own tools: the <tool name>'s <parameter>

There are also after hooks. An after hook runs once the step is done, and the result is whatever the step gave back.

Note that a hook can't change the step it is looking at, and it can't run a program, not even through a tool.

Hooks say yes or no, and that's it.

Ask Me Before a Push

A git push is (often) public and can be an egress mechanism for external communication, one leg of the lethal trifecta.

Let's say you want your agent to ask you before it pushes anything, so you can be sure.

Here's a hook to accomplish that:

AGENTS.talk
before git
  if the git's operation is 'push'
    ask 'Push this branch to origin?' with 'yes' or 'no'
    if the result is not 'yes'
      reject 'The human said no'
    end
  end
end

The ask command is special: only a human can answer it and the agent can't get around it.

If you wander off and don't answer within 2 minutes, the result is null, which means no.

Git In ClankTalk

Note that the git in ClankTalk is already reasonably locked down:

Also, if your project is a subfolder of a bigger repo, git status, diff, show and log only cover your project

Keeping Agents Out of Migrations

Maybe your company has a policy that all database migrations, found in /migrations/ must be written by humans. (Fair!)

You could enforce this policy with the following hook:

AGENTS.talk
before change
  if the change's path is in @/migrations/
    reject 'A human writes the migrations'
  end
end

Note that change covers every sort of file change: write, append, replace, line edits, move, delete and undo. It also covers git commands that change files, like git merge or git stash, and a move into the folder, not just out of it.

Deploys

Let's say you're OK with your agent deploying to staging whenever it likes. (Brave!)

Production, however, is a bit much, and you'd like to be asked.

Here's a deploy tool that asks you before any deploy to prod:

AGENTS.talk
# Deploys the app: staging or prod.
tool deploy with target
  if target is 'prod'
    ask 'Hey boss, deploy to prod?' with 'yes' or 'no'
    if the result is not 'yes'
      reject 'Not today, robot'
    end
  end
  run 'fly' with 'deploy', '--app', 'shop-' + target in a sandbox with network
end

A reject in a tool stops the tool, and your agent gets the message back as an error.

Now, if the rule lives somewhere else, say in a team policy, you can also hook into your own tools, not just the built-in steps. Inside the hook, each parameter goes by its own name:

AGENTS.talk
before deploy
  if the deploy's target is 'prod'
    reject 'Prod deploys go through CI'
  end
end

By the way, that 'shop-' + target might make you a little itchy around injection attacks, but run never goes through a shell.

Each value after with goes to fly as exactly one argument, so a strange target can only change the app name. It can't sneak in a new flag or a second command.

Less Test Output

Test runners typically output tremendous amounts of text. A failing suite can easily dump thousands of lines on your agent, eating up context.

In ClankTalk, you can trim the output of a chatty programs when you run it, using the keeping clause:

AGENTS.talk
tool run_tests
  run 'npm test' in a sandbox keeping the last 200 lines
end

tool lint
  run 'npx eslint .' in a sandbox keeping lines matching pattern /error/
end

You can keep the first <n> lines, the last <n> lines, both, or only lines matching a pattern.

ClankTalk puts a single line like ... 1834 lines cut ... where the rest used to be. You can add or 20 KB to any of these to cap the size, too.

Nothing is lost, though: the result's full has every line, so your agent can dig in if it really needs to.

The same keeping works for everything a script prints, with a line at the top of AGENTS.talk, like limit output keeping the last 400 lines. The full text is then in the previous output. ClankTalk already cuts at 1000 lines or 30 KB by default, so you will rarely need it.

ClankTalk Audit History

ClankTalk writes down every step that touches the world: every read, change, run, git command, fetch, ask and tool call, along with the script that did it. You can watch it live:

 
tail -f .talk/audit/*.log
 
-- session claude-code-4f2a --
== 10:42:01  script 12  Raise the retries  (done, 4 steps) ==
  in @/src/config.js
    replace 'retries: 3' with 'retries: 5'
  run_tests
--
10:42:01  replace  src/config.js                             1 match, line 12
10:42:01  tool     run_tests
10:42:09    run    npm test                                  sandbox, local network, code 0, 7.9 s

The history goes to .talk/audit/, one file a day, and scripts can't change it. Your agent can read its own steps with the audit, which is handy when it wants to remember what it just did.

By default, ClankTalk keeps three days of history.

If you wanted to keep a week, you could add this to your AGENTS.talk:

AGENTS.talk
keep audit for 7 days
audit as json

Adding audit as json makes each line of the file a JSON object, so jq and friends can work with them.

Sharing Tools

Once you have a nice set of GitHub tools, you'll probably want them grouped together and maybe moved out of your AGENTS.talk file.

You can put them in their own .talk file and import it:

AGENTS.talk
import ./talk/github.talk

Note that a ./ path starts from the file doing the importing and @/ starts from the project root.

Just like AGENTS.talk, your agent can't change anything in talk/, or any .talk file for that matter, so your shared tools stay safe.

Taint & Reading GitHub Issues

OK, now we are going to cover a feature that, we're going to be honest, we're not 100% sure on: tainting.

Tainting has a long history in programming languages. Perl's taint mode, for example, won't let data from outside the program, like command line arguments or environment variables, reach a shell command or a file write until you check it with a regular expression.

We are trying to come up with something similar around prompt injection, and this is our current thinking on the problem.

Let's motivate the discussion with an interesting use of agents near and dear to our hearts: sifting through issues on GitHub.

Before we get into ClankTalk's taint infrastructure, let's set up a subagent with a restricted tool set to do GitHub issue analysis for us:

AGENTS.talk
allow secret ANTHROPIC_API_KEY for subagents
use model 'claude-sonnet-5-5' for subagents with secret ANTHROPIC_API_KEY

# Shows a GitHub issue.
untrusted tool issue with id
  run 'gh' with 'issue', 'view', id in a sandbox with network,
    reads of '~/.config/gh'
end

# Summarizes the most important open issues.
tool summarize_issues
  run 'Summarize the most important open issues' in a subagent with only issue
end

A subagent is a separate model that can only call the tools you give it. Here, the subagent can call issue and nothing else, and the tools you give a subagent can't write files, fetch or use git.

So even if a prompt injection happens in an issue and talks the subagent into something, there isn't much it can do.

Don't get too comfortable with the subagent, though: it read the public issues, so its answer to the calling agent untrusted too. It taints your session just like the issues would: yes, it's harder to pull off, but still possible.

After your agent reads the sub-agents response, the whole session is tainted.

How Tainting Works

Three things can taint a ClankTalk session:

Each time, ClankTalk saves a copy of the text in .talk/untrusted/<session>/, with where it came from and when.
Your agent cannot change or delete these files, so you can always see what it read.

By default, while your session is tainted, two actions require confirmation:

There are of course other actions that can potentially compromise you, and you can add them to taint checking:

AGENTS.talk
taint asks before push, secrets, commit, network runs, new hosts

where:

For anything else, a hook can check the session is untrusted itself:

AGENTS.talk
before deploy
  if the session is untrusted
    reject 'No deploys after reading untrusted text'
  end
end

A hook can also list the session's untrusted inputs, if you want to say which ones are in the way.

Now, why is the default to only ask on push and fetch?

Because of the well known psychological problem of decision fatigue.

We are trying to balance security with usefulness of this features, so people actually use it. We'll see how it goes.

Untainting

Taint stays until you approve the untrusted input. It covers the whole project, not one conversation: if you resume a conversation, the injection is still in your agent's context, so a new session starts tainted too.

You can approve inputs in three places.

When ClankTalk asks you about a risky action, the question gives the untrusted content:

Push to origin? 2 untrusted inputs are not approved:
tool issue (42), https://api.github.com/...
Review them with: npx clank-talk taint

You can also use your terminal to look at tainted content:

 
$ npx clank-talk taint                  # list the inputs
$ npx clank-talk taint show 2           # read one
$ npx clank-talk taint approve 1 2      # approve some
$ npx clank-talk taint approve --all    # approve all

Your agent has no shell in ClankTalk, so it cannot run these commands.

Or, without leaving your harness, ask your agent to run taint review. It shows you a form with a table of the inputs that are not approved, and a box to approve each one. Only you can fill in the form.

LLM Judging

If you do not want to read every input yourself you can use a judge model can read them for you and say clean, suspect or unsure:

 
$ npx clank-talk taint judge --all
$ npx clank-talk taint approve --clean

Note that the judge reads the same text as your agent, so a prompt injection attack can fool it too.

Configuration

Here are all the configuration options for taint:

AGENTS.talk
# on (the default), off, or required: required means no one can turn it off
taint required

# how much of each untrusted input to keep for review (1 MB by default)
keep untrusted inputs up to 5 MB

# the judge: a model that reads each input, and says clean, suspect or unsure
use model 'claude-haiku-5-5' for judging with secret ANTHROPIC_API_KEY

# the judge needs the key, as a subagent does
allow secret ANTHROPIC_API_KEY for subagents

# judge each new input when it comes in (else: npx clank-talk taint judge)
judge untrusted inputs automatically

# a clean verdict approves the input, so you get no question for it
let the judge approve clean inputs

Creating An Agents.talk File

A AGENTS.talk file is declared in the following order:

  1. The top lines: allow, use model, limit, keep audit and taint
  2. Imports and tools
  3. Hooks, which run in the order you write them

See the AGENTS.talk reference for more details.

A suggested prompt for a non-ClankTalk agent to generate an AGENTS.talk is:

 
Read <link> and walk me through creating an AGENTS.talk file for this project

Policies for Teams

If you're running agents for a team, you probably want some rules that individual projects can't loosen. That's what a policy is for: a file that ships with the harness and sets limits no AGENTS.talk can widen.

See Policies for Teams for the details.

Reading ClankTalk

We don't expect you to write much ClankTalk, but you are going to probably want to read some of it. We hope that it's pretty obvious what's going on, at least when compared with, say, bash.

Still, probably worth a quick tour of the language:

 
# this is a comment
set robot to {name: 'clank', tags: ['agent', 'robot']}
print 'Hello, ${robot's name}!'

if robot's tags contains 'agent'
  print 'an agent'
end

replace
| const retries = 3;
with
| const retries = 5;
in ./src/config.js

If you run into something you don't recognize, every command is in the Docs for Agents.

Questions From ClankTalk

ClankTalk will occasionally stop to ask you something. You get a question when:

Undo

The ClankTalk runtime maintains an undo list of all operations it has seen, so either an agent or you, the human, can request that changes be undone.

Here is an example of an agent making a change, running a test and, if the test fails, undoing the change:

 
in ./src/config.js replace 'retries: 3' with 'retries: 5'
run_test_file @/test/config.test.js
if the result's code is not 0
  undo
  print 'The test failed, so the change is undone'
end

Harness and Agent Notes

Work in progress

Notes for specific harnesses and agents are coming soon.

Limitations

We hope that ClankTalk is a step forward in providing a safer environment for agents to work in. It can feel a little constricting at first when you initially build out your AGENTS.talk file, but that's probably a good thing.

That being said, ClankTalk isn't foolproof:

And there are probably holes in the implementation as it stands. We'll try to get them patched up, but we think the basic idea is sound.

Hope it helps!