Skip to content

5 · Skills and hooks

What you will have done: understood what to put on the machine your agent runs on, and which of it stops a mistake rather than explaining one.

A connected harness can reach the endpoint. That is not the same as knowing how to use it. Two kinds of thing close the gap, and they work differently enough to be worth separating.

A skill is knowledge. It is read by the model, tells it which tool answers which question and in what order, and can be ignored, because a model reading instructions is a model that may read them badly.

A hook is machinery. It runs in the harness before or after a tool call, it is not a model, and it cannot be talked out of a decision. A hook is what you use for a rule that must hold.

    flowchart LR
  P["What you asked for"] --> M["The model<br/>reads the skills"]
  M --> C["It composes a tool call"]
  C --> K{"The hooks<br/>run here"}
  K -->|"refused"| S["A sentence, and no call"]
  K -->|"allowed"| E["POST /mcp"]
  E --> G{"The instance's own gate"}
  G -->|"refused"| S2["A sentence, and no write"]
  G -->|"allowed"| W["The write, and the audit row"]
  W --> L["The local ledger<br/>after the call, either way"]
  S2 --> L
  

Two gates, and only the right-hand one is Neutron’s. The left one is on your machine, it is yours, and it exists so a refusal costs a local exit rather than a round trip.

The skills

neutron-connect is everything on the connecting page as a procedure the agent carries out for itself: mint a token, store it in the token file and in the estate’s secret store, register the server with whichever harness is running, verify with tools/list, and rotate it later. It carries two scripts, one that registers an instance and one that verifies it, and the registration script has a dry run so you can see what it would write before it writes it.

neutron-revenue is the operator playbook, organised by journey rather than by tool. For each of the fifteen Revenue Engine journeys it names the tool or route that does the step, the order to do them in, the rule that step has to keep, and the sentence you get when you break it. It opens with seven rules stated flatly, which is the part most worth reading before the first mutating call:

  1. An agent may draft. An agent may never send.
  2. A people_import dry run is the default, and it is there to be read.
  3. An imported or found address is never VERIFIED. Only a delivery earns that.
  4. The person spine is forward-only.
  5. Every send and every reply is a touch on one ledger.
  6. A held letter names the condition that failed.
  7. A correction beats a reading, and suppressions are permanent.

The five vendor skills cover the cold-email stack underneath the engine: cold-email-fleet as the umbrella, and clayinbox, primeforge, warmforge and salesforge for the individual vendors. They are the knowledge you need when something below Neutron is wrong: a mailbox that will not connect, a warm-up switch that defaults off, a plan that gates an API.

They carry one rule in common.

Through Neutron first. Where Neutron has a seam for something, go through Neutron’s own tools rather than the vendor’s API. Go vendor-direct only for what Neutron does not have. The reason is the ledger: a mailbox changed through Neutron is a change with an audit row and a screen that knows about it, and the same change made at the vendor is a fact about your estate that your instance has no way to learn.

The hooks

Three, and each one exists because of a specific way a session goes wrong. All three match any server whose name begins neutron, so one installation covers every instance you have registered.

The wire guard

It runs before an api_request or admin_request call and refuses the ones that put something beyond recall into the world. It covers the same ground the wire scope covers, from the other side of the wire. Both tools go through it because a wire route composed through either is the same letter, and every route the wire scope guards sits under /api/admin/, so the wider tool reaches nothing the guard’s list does not already hold.

Two of its refusals are worth quoting, because they are the two people argue with.

An agent may draft. An agent may never send. This call puts a letter on the wire; a person does that in the Inbox or Approvals.

A suppression is permanent and outranks everything, including a spreadsheet that says otherwise. Lifting one is how somebody who asked to be left alone gets written to again, and no agent does it.

The rest name the specific harm rather than the rule: rotating a receiver secret stops every sequencer account that is not re-pointed in the same breath; resuming a paused domain puts every letter behind it back on the wire, across every campaign, after the warden paused it on evidence; creating a sending identity is handing out the right to send as an address.

One of them guards the credential itself, and its reasoning is worth having:

An agent may not mint its own credential. A person does that in Settings > Access & Security > API, and an agent that mints one cannot be revoked by taking its credential away.

That is the same rule the instance enforces with a token cannot mint tokens, arrived at from the other end. Revocation only works while somebody else holds the only copy.

Two of its checks read the body rather than the path, and both are there because the harm hides one level down. A campaign’s Setup route is refused only when the body carries the review policy, the no-human-review confirmation or the identity pool, and it looks for those fields anywhere in the body, because one nested object would carry the field straight past a check that only read the top level. Setting a sending identity active is refused; deactivating one is not.

It refuses a path it cannot read. A path that is not canonical is denied on the grounds that what it reaches cannot be told from looking at it, which is the same reasoning the endpoint itself uses.

It also fails closed on its own dependencies. Without jq it refuses every call it matches and says so, rather than waving them through while appearing to be installed.

The import guard

It runs on both sides of a people_import call. Before a write, it refuses unless a dry run for the same file ran earlier in the same session. After a successful dry run, it records that one happened.

What makes it precise is what it keys on: the session, the server, the campaign, the filename, and the sha256 of the CSV itself. A dry run is therefore a permit for one file, not a standing permit. Change a byte and the permit no longer matches, which is the same rule the endpoint enforces when it refuses to write a file whose preview it did not read.

It reads dry_run the way Neutron does. Only the literal false is a write; anything else only reports.

The ledger

It runs after every mutating call and appends one line naming the time, the server, the tool, a hash of the arguments and whether it succeeded. It blocks nothing, fails no turn, and logs no value — the arguments are hashed, never stored.

The instance audits what it did. This is the other half: what this harness asked of which instance, so a session’s changes can be reconstructed from the machine that made them.

It classifies by name rather than guessing, and four tools are conditional: an api_request and an admin_request count only on a write method, a people_import only on the literal false, and a people_resolve_email only when it stores a verdict or may spend money. approval_decide is recorded and approvals_inbox, which only reads, is not. A tool the instance grows later falls through and is not recorded until its name is added, which is a deliberate choice to under-record rather than to mislabel.

Installing them

One installer does both halves. Each skill directory is symlinked into $HOME/.claude/skills, and the installer refuses to overwrite a target that already exists and is not a symlink, saying which it skipped.

revenue-engine/install.sh --dry-run    # print what it would link, change nothing
revenue-engine/install.sh              # skills only
revenue-engine/install.sh --hooks      # skills, and merge the hooks into your settings

Hooks are opt-in. Without --hooks the installer says plainly that it did not install them and how to ask for them; --no-hooks is the explicit form of the same thing. --settings <path> points the merge at a settings file other than the default, which is what you want for a project-scoped installation. The merge needs jq and is idempotent, so running it twice changes nothing the second time.

Where they come from

The skills and hooks ship with Neutron itself, in the revenue-engine/ directory of the Neutron source repository, beside the API that they drive; the installer above is revenue-engine/install.sh. The source is private. If you cannot reach it, the useful thing to take from this page is the shape rather than the files.

A rule you need to hold belongs in a hook, not a paragraph. Anything the model merely needs to know belongs in a skill. A hook should fail closed when its own dependencies are missing, and should say so. And the two lists worth writing first are what never to do, and which steps require a person.

Last updated on