Skip to main content

allem-cli — connect your coding agent to Allem

Allem records what your coding agent does. It blocks only the actions you have explicitly marked as hard gates in your rules.

That sentence is the product. Everything below is detail.

allem init installs a hook into your coding agent's own configuration. From then on, every tool call it makes — shell commands, file edits, file reads — becomes an event in your organization's tamper-evident audit chain, with secrets redacted before anything leaves your machine.


Install

pip install allem-cli && allem init --key alm_sk_live_...

Your key comes from the Allem console: Get Started → Connect your agent, or from the page you land on when you confirm your email after signing up. Without --key, allem init asks for one.

That key is not what ends up on your machine. It is used once, to give each of your coding agents its own key, and is never saved to disk. See What allem init does below.

Python 3.11 or newer. macOS and Linux.


What allem init does

  1. Finds the coding agents on this machine. It offers only what it finds, and names the file it will write for each one.
  2. Exchanges your key for one key per coding agent, before writing anything at all. The key you pasted is held in memory for the length of one request and is never written to disk. See The key on your machine below for why this matters.
  3. Writes ~/.allem/config.toml, mode 0600, holding the endpoint, one agent key, and one agent identity per coding agent.
  4. Registers the hook in each agent's config. It reads the file, merges its entry, and writes it back — the original is copied to <file>.allem-backup-<timestamp> first. Anything already in there is left exactly as it was. Running allem init again replaces Allem's entry rather than adding a second one.
  5. Verifies. It sends one real event with the agent key and tells you whether it was accepted, in words: accepted, key refused, endpoint unreachable, or agent unknown.
  6. Reads your gated-action list so the first tool call of your first session is already checked against your own rules (see What gets checked).
  7. Prints where to look in the console.

If step 2 cannot be completed, allem init stops and tells you what is missing. It writes nothing — in particular it does not save the key you gave it. There is no fallback, because the fallback would be the exact thing this is here to stop.

The key on your machine

Allem has two kinds of key, and they do not look different:

An organization key An agent key
Send events, ask for checks ✅ for its one agent
Read every event in your account
Create and delete agents
Change the rules your agents are judged against

The key you paste into allem init is an organization key. Leaving one on a developer's laptop means the machine being watched holds the credential that edits the rules it is watched against — so allem init trades it for agent keys and keeps only those. What is left on your machine can send events for your agents and ask for checks. That is all it can do.

If you already have an agent key, pass that instead: allem init recognises it, uses it directly, and does not exchange anything.

Options

Flag What it does
--key Your Allem API key. Prompted for if omitted. Held in memory only; never written to disk.
--endpoint Allem API base URL. Defaults to https://api.allem.ai.
--agent Your identity on this machine. Defaults to user-hostname.
--agent-id Override your agent's canonical id. Normally unnecessary — the exchange returns it.
--only claude-code, codex, gemini-cli — comma-separated. Skips detection.
--scope project Configure this project only, not the whole user account.
--production-url-pattern Regex for URLs that mean production, for the built-in floor.
--no-verify Skip the test event.
-y, --yes Don't ask for confirmation.

There is no flag for the connection tier, and that is deliberate — see How your connection is recorded.


allem provision — a key for an integration you wire yourself

allem init is for coding agents. If you are wiring the Allem SDK, the MCP server or a proxy, this mints the key for it and records which one it is:

allem provision --agent billing-bot --for sdk

It asks for your organization key (or takes --key), trades it for one agent key, and prints that key once. It writes nothing to disk — not the key you pasted, and not the key it minted. Put it wherever your integration reads its credentials from.

--for is required and there is no default. Allem records the answer as a statement about how this agent is connected, and it appears in your evidence export; a guessed default would end up there too.

Flag What it does
--agent The agent's id. Created if it does not exist yet. Required.
--for sdk, mcp or proxy. Required.
--key Your Allem organization key. Prompted for if omitted. Never written anywhere.
--endpoint Allem API base URL. Defaults to https://api.allem.ai.
--key-name Name for the new key in your console's key list.

If Allem already shows an agent as "connected over the API, connector not identified", this command is what declares a connector for it. That state is not an error — it means nothing declared itself, so Allem records that it cannot tell — but it is the one thing on that screen you can change.

What it does not do is erase the old record, and that is deliberate. An agent that has been sending on an undeclared key has genuinely connected both ways, so after this it reads "Connected more than one way" and names both, with the date each was last seen. Allem never collapses that to the newer connection: events that arrived with nothing identifying them are not retroactively covered by a declaration made afterwards. New traffic from the declared key is recorded as that connector from the first event.


What each supported agent needs

Coding agent File written Events used Status
Claude Code ~/.claude/settings.json PreToolUse, PostToolUse Verified. Allem has run on this shape against its own repo for weeks.
Codex ~/.codex/hooks.json PreToolUse, PostToolUse Written from Codex's documented event names. Not yet exercised against a live install.
Gemini CLI ~/.gemini/settings.json BeforeTool, AfterTool Written from Gemini CLI's documented event names. Not yet exercised against a live install.

Two things follow from that last column, and we would rather say them than have you discover them:

  • Codex also supports a [hooks] table in ~/.codex/config.toml. allem init writes hooks.json and never rewrites your TOML — round-tripping it would drop your comments and formatting. If a [hooks] table is already there, allem init says so and allem status prints the exact command line to add by hand.
  • If a registration does not fire on your setup, allem status shows what was written where. The shape is one small adapter per agent (allem_cli/adapters.py), not an integration — corrections are cheap.

Each coding agent gets its own agent record: identity dev-laptop plus the agent's name, e.g. dev-laptop-claude-code. Two connectors on one machine are two records, because a Claude Code record must not silently inherit a Codex record's posture.


What is recorded

Every tool call, as an action with redacted parameters:

The agent does Allem records
Runs a shell command bash.execute, git.commit, git.push_force, test.run, docker.run, net.request — with the command shape
Edits or writes a file file.edit / file.write, the path, how many characters changed
Reads a file file.read, the path
Touches a secrets path file.access_secrets
Anything else tool.<name>

Secret values never leave your machine. Before any payload is built, five redaction rules run over anything that would travel: environment assignments (FOO_API_KEY=…), secret CLI flags (--token=…), authorization headers, known credential prefixes (Allem, Anthropic, GitHub, AWS, Slack, JWT), and any 40+ character opaque token. Commands are truncated at 300 characters. What travels is the shape of the command and the paths, not the contents of your files.

Events are written to a local spool on disk before any network attempt, so an Allem outage costs you nothing and loses nothing: the record survives and is delivered when connectivity returns.


What gets checked before it happens

Two sources, and they add — the second never narrows the first.

  1. Your own rules. The hook fetches your agent's gated-action list from Allem (GET /v1/agents/{id}/scopes/hard-gates) and caches it against its scope version. Any action on that list is checked before it runs.
  2. A built-in floor: force-push, secrets paths, production URLs. Always checked, whatever your rules say.

This matters more than it sounds. If the check were driven by our idea of "risky" instead of your rules, a rule you wrote denying file.edit under /config would appear in the console, read as active, and never fire — because nothing would ever ask. A rule that lies is worse than no rule.

The same list decides what can be blocked: an action is stopped only if it is on this list and Allem denies it. Checking and blocking come from one source, so there is no second place for the two to disagree.

If the list cannot be read, the hook falls back to the floor alone, blocks nothing, and writes that to ~/.allem/log/hook.log every time it happens. Checking less than you configured is sometimes unavoidable. Doing it quietly is not.


What is and is not blocked

One thing can stop a tool call: an action you marked hard_gate in your rules, which Allem then denies. Nothing else, ever.

Ordinary denials are recorded and proceed. That is a decision, not a missing feature. Hooks across all three agents are, in Codex's own documentation, "a guardrail rather than a complete enforcement boundary because Codex can often perform equivalent work through another supported tool path." A denied rm in a shell does not prevent the same deletion through a file-edit tool. Blocking everything your rules deny would claim more prevention than a hook can give — which is precisely the overclaim Allem exists to criticise in other people's products. Marking a rule hard_gate is you saying "this one is worth failing closed for", which is your call and not ours.

Two consequences worth stating plainly:

  • Nothing is blocked when we cannot read your gated-action list. We do not enforce a rule we cannot see. The narrowing is written to the log every time it happens.
  • An unhappy answer is not a denial. A revoked key returns allowed: false too. The hook only honours a denial when Allem returned a verdict it actually recorded, so an expired credential cannot turn into a broken editor.

When something goes missing

This is the part that decides whether Allem is a tool you keep. The only thing that may ever stop a tool call is an explicit deny from Allem for a hard-gated action. Every other failure — ours, yours, or the network's — lets the action through and is written down.

Two things make that true rather than aspirational. The hook process always exits 0: a decision travels as JSON on stdout, which is how all three coding agents read one, so no crash, missing file or timeout can be mistaken for "block". And the registration runs a generated guard script (~/.allem/bin/allem-hook) whose only job is to check every path before running anything.

What goes missing What your coding agent sees Where it is recorded
~/.allem/config.toml deleted Nothing. Tool calls proceed, nothing is recorded. guard.log
Config unreadable or malformed TOML Nothing. Tool calls proceed. hook.log, config_unusable
The hook module deleted (package uninstalled, venv rebuilt) Nothing. Tool calls proceed. guard.log, missing …/hook.py
The Python interpreter deleted Nothing. Tool calls proceed. guard.log
The whole ~/.allem directory deleted Nothing. Tool calls proceed. nowhere — there is nowhere left to write
Allem unreachable Nothing. Tool calls proceed; events spool locally and deliver later. hook.log
Key invalid or revoked Nothing. Tool calls proceed. Never treated as a denial. hook.log
Allem slow or hung Nothing, after at most 5 seconds. hook.log, deadline_exceeded
Malformed response, unparseable input, a bug in our code Nothing. Tool calls proceed. hook.log
Your gated-action list unreadable Tool calls proceed. Nothing can be blocked. hook.log, every time

None of these leaves your coding agent erroring, and none of them requires you to know that deleting Allem's config is the fix.

This is written from an incident rather than from imagination. Allem's own repository ran an earlier hook whose guard checked the interpreter and the config file but never the script it was about to run. When that script moved, both checks passed, the shell exec'd Python against a deleted path, Python exited 2 — and a PreToolUse hook that exits 2 means block this tool call. Every tool call in the repository began failing. The trailing || exit 0 did not help, because exec replaces the shell: once Python starts there is no shell left to run the || branch.

So the guard is a generated file with its dependencies in one list, not a shell chain that the next person extends incorrectly, and there is a test for every row of the table above — including the exact combination that caused it, config present and entry point missing.


What Allem can and cannot see

Allem sees the actions your coding agent takes through its own hook system, and records them. Actions taken another way are not visible to Allem.

This is a coding-agent hook connection — tier 2 of Allem's connection-tier model. Real tool calls, incomplete coverage. It is not the same assurance as an agent wired through the Allem SDK, where every action passes through check() before it happens, and Allem's console must never show the two as equivalent.


How to uninstall

allem uninstall            # remove the hook registrations
allem uninstall --purge    # also delete ~/.allem (config, local log, spooled events)

uninstall removes exactly Allem's entries from each config file and leaves everything else — your own hooks, your settings — as it was, backing the file up first. Without --purge your config and local log stay where they are, and --purge refuses to run while events are still waiting to be delivered unless you pass --yes.

Events already recorded stay in your organization's audit chain. An audit log you could erase by uninstalling the thing that wrote it would not be an audit log.


Configuration

Three values, three layers. Lowest precedence first:

  1. ~/.allem/config.toml — what allem init writes
  2. ./.allem/config.toml — optional, per project
  3. ALLEM_ENDPOINT, ALLEM_API_KEY, ALLEM_AGENT_EXTERNAL_ID — environment, wins over both
endpoint = "https://api.allem.ai"
api_key = "alm_sk_live_..."          # an AGENT key, not the one you pasted
agent_external_id = "dev-laptop-claude-code"
agent_id = "665f…"

[codex]
agent_external_id = "dev-laptop-codex"
api_key = "alm_sk_live_..."          # its own agent key — one per agent
agent_id = "665f…"

What is in the file is what Allem sees. The identifier is used exactly as written — nothing is appended at runtime. Each coding agent gets its own record, its own key and its own [section], which allem init writes for you: an agent key is bound to one agent, so two coding agents that are two agents need two keys. allem init --agent <id> makes them all one agent instead, and they then share the one key.

Optional: production_url_pattern.

Everything else lives under ~/.allem/: bin/allem-hook (the guard), state/ (spool, per-session sequence numbers, the cached gated-action list), log/hook.log and log/guard.log.


Limits worth knowing

  • An agent key cannot look itself up. Closed on 2026-08-10. The credential exchange returns the canonical agent_id and allem init writes it into the config, so the hook can read your gated-action list on the agent-key path without anyone passing --agent-id by hand. The flag survives as an override.
  • One agent key serves one agent. If you hand allem init an agent key while two coding agents are installed, it refuses rather than half-working: the platform would reject every event from the second one. Run it once per coding agent with --only, or use an organization key, which gives each of them its own key in a single run.
  • Registration for Codex and Gemini CLI is unverified against a live install — see the table above. The deny shape for those two is written from their documented behaviour; if it is wrong, the agent ignores it and the action proceeds, which is the right way for that uncertainty to land.
  • Per tool call, one process. The hook starts a Python interpreter for each tool call. Unchecked calls exit before importing anything that touches the network, and the whole process has a hard 5-second ceiling (ALLEM_HOOK_DEADLINE_S).

How your connection is recorded

An agent connected by a coding-agent hook must not display the same assurance as one wired through the SDK, so Allem stores how each agent is connected and shows it wherever its governance is shown. The value is never taken from anything an event says — the agent being judged does not get to choose how trustworthy its own record looks. It is stamped on the key at the moment the key is issued, using your organization key, which the agent does not hold.

How you got the key Recorded as
allem init a coding-agent connection (Tier 2)
allem provision --for sdk the Allem SDK (Tier 1)
allem provision --for mcp the Allem MCP server (Tier 3a)
allem provision --for proxy a proxy (Tier 3b)
A key created in the console "connected over the API, connector not identified"

allem init always declares a coding-agent connection and has no flag to say otherwise. It installs a hook; a hook does not block anything while Allem is unreachable, and if this command could be told to claim Tier 1, your coverage report would state that a hard gate held during an outage when it did not.

Allem describes these as declarations, never as something it observed: once events start arriving, the SDK, a coding-agent hook, an MCP server and a proxy are indistinguishable on the wire.


For María — what a customer actually experiences

A developer at a company that just signed up opens the Allem console and lands on Get Started. Step one says "Connect your agent". They pick Claude Code and see a single line with their key already filled in, which they copy and paste into a terminal.

It takes about ten seconds. The command tells them what it found on their machine — "Claude Code → /Users/them/.claude/settings.json" — asks once whether to write to it, and then reports each step in plain words: the configuration file it wrote, that it backed up their existing settings first, that it sent a test event and Allem accepted it, and how many of their own rules are now being checked. It finishes by telling them exactly where to look in the console, and that they can run allem uninstall whenever they want.

They go back to work. The next time they use Claude Code, every command it runs and every file it edits appears in Allem — with passwords and API keys stripped out before anything is sent. In the console, the Get Started step turns green and they can watch their own agent being observed, which is the first moment Allem is real to them rather than a description.

What they are told, in the terminal and in the console and in this document, is that Allem records what their coding agent does, and blocks only the actions they have explicitly marked as hard gates. Out of the box that is nothing at all: a new customer has no hard gates, so on day one Allem watches and never interrupts. The first time it stops something will be because they asked it to, on a rule they wrote.

They are also told that Allem only sees what the coding agent does through Claude Code — if the same person deletes a file by hand, Allem does not know. We say that first, before they discover it, because a customer who finds out later that a tool saw less than they assumed does not trust the parts that were true either. Nothing else about Allem works if that is not the habit.

And the thing they will never see, which took an incident to get right: if any piece of Allem goes missing from their machine — our config, our code, our server — their coding agent carries on exactly as before. It does not error, it does not stall, it does not ask them to debug us. A governance tool that breaks a developer's session because we shipped a bad path is uninstalled within the hour, and rightly. The only interruption they will ever get from us is the one they configured.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

allem_cli-0.1.0.tar.gz (106.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

allem_cli-0.1.0-py3-none-any.whl (74.9 kB view details)

Uploaded Python 3

File details

Details for the file allem_cli-0.1.0.tar.gz.

File metadata

  • Download URL: allem_cli-0.1.0.tar.gz
  • Upload date:
  • Size: 106.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for allem_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 943d3c1e2accb508b1ada0406ee36ea5ff2ca3022567bc1223708fbd66fa5006
MD5 22c77b9819859bac6b08342782d12193
BLAKE2b-256 af78ff2d3d84461c020970b49be2aa585d0a3d7a34b6a41f25305f36d68992c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for allem_cli-0.1.0.tar.gz:

Publisher: publish.yml on Allemai/allem-platform

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file allem_cli-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: allem_cli-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 74.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for allem_cli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 439413c76262a540c53cea4093042ab11930a9842a109fb4b0b244a236fcb39d
MD5 fb0207c2ad542890270a25df20b2b57a
BLAKE2b-256 af5be4384fd3a34620e54afc3ae2165a8db90b05e4f422b29cd244dcc38c66d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for allem_cli-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Allemai/allem-platform

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page