contenox
Browse docs/

Tutorial: your first agent

An agent is a file. This tutorial writes one, shows you the two things contenox builds behind it, and runs it — because the difference between those two things is the part worth understanding.

If you already have agents in .claude/agents/, skip to Agents you already have. They work as they are.

Write it

mkdir -p .contenox/agents

.contenox/agents/reviewer.md:

---
name: reviewer
description: Reviews a Go file for correctness problems
tools: read_file, write_file
---

You are a Go code reviewer. Read the file you are asked about using the tools
you have, then list concrete correctness problems you can point at in the code
you actually read.

Ground every claim: quote the lines you rely on before drawing a conclusion. If
a tool returns nothing or errors, say so and stop rather than guessing. Be
brief.

That is the whole agent. No build step:

contenox agent list
NAME      SOURCE      KIND   ENABLED
reviewer  discovered  chain  true

Run it

contenox mission fire reviewer "review payments.go" --wait

Reading files is allowed under the default posture, so it runs without asking. Ask the same agent to write a file and it stops for approval.

What sits behind it

Your one file became two: a chain that says what happens, and a policy that says what is permitted. Your declaration had no way to separate those — it had one tools: list.

Part of the policy:

{
  "default_action": "approve",
  "rules": [
    { "tools": "local_fs", "tool": "*", "action": "deny",
      "when": [{ "key": "path", "op": "glob",
                 "value": "**/{.ssh,.aws,.kube,.config/gcloud}/**" }] },
    { "tools": "local_fs",    "tool": "read_file",   "action": "allow" },
    { "tools": "local_fs",    "tool": "write_file",  "action": "approve" },
    { "tools": "local_shell", "tool": "local_shell", "action": "approve" }
  ]
}

The credential deny is added to every agent and comes first, because rules are first-match-wins.

Add permissionMode: acceptEdits to the frontmatter and write_file becomes allow; local_shell stays approve.

You do not maintain these files. Edit the declaration; they follow.

Agents you already have

Nothing to move or convert:

your-project/
  .claude/agents/reviewer.md    ← found where it is
  .contenox/agents/triage.md    ← yours

Both are agents. Ones from another tool are prefixed with it (claude-code-reviewer); your own keep their name.

Connecting a tool

WebSearch is not a tool contenox hosts. Name it and the agent runs without it:

not carried  tools: WebSearch    resolves to nothing connected here; the agent
                                 runs without it

Connect it as an MCP server, an OpenAPI spec or a shell command, then give it the name your declaration already uses:

# .contenox/agents.toml
[tools]
WebSearch = "tavily.search"

It matches no policy rule, so it falls to default_action and asks you on every call. To let it run unattended:

[[policy.always_allow]]
tools = "tavily"
tool = "search"

The knobs

A declaration says one prompt, one tool list, one model, one permission setting. Context budget, retries, loop bounds, shell allowlists and compute ceilings live in agents.toml, which is commented and applies to every agent at once:

[chain]
token_limit = 131072

[tools_policies.local_shell]
_allowed_commands = "ls,cat,git,go"

Next: Declaring agents for the full frontmatter, or HITL policies for the other half.

Esc to close