Governed agent platform built on EngramPort GraphRAG memory. BB4C — Breath Before Code.
Project description
ClawTex
BB4C. Breath Before Code. Built by DeVere Cooley, AN2B LLC
ClawTex is a governed AI agent with a typed graph memory. It runs from one
pip install, governs every tool call before it executes, and remembers across
sessions in a real vector and graph store instead of flat files.
It is reactive: it answers messages. It is not an autonomous planner and does not decompose goals on its own.
What it is
ClawTex is an agent runtime built around three things that are wired into the code, not the pitch:
- Governance on every tool call. The Warden policy engine evaluates each tool call and returns ALLOW, DENY, or REVIEW before the call runs.
- Typed graph memory. Memory lives in EngramPort,
a vector and graph store. Exchanges are recalled before each response and
ingested after. Durable lessons and synthesized observations are written as
typed graph nodes (
principle,insight) so grooming and consolidation run over a connected graph. - Bring your own LLM. Anthropic ships in the box. OpenAI and OpenAI-compatible endpoints are an optional extra.
You provide a SOUL.md to set the agent's identity. If you don't, a default persona is used.
Why
Most agent starter kits give you a chat loop, some tools, and a system prompt. That leaves three gaps ClawTex closes in code:
- Policy enforcement. You should be able to stop an agent from deleting a file or sending an unauthorized message. Warden gates each tool call.
- Persistent memory. The agent recalls prior context before answering and records the exchange after. Memory is typed and graph-linked, not a log file.
- An audit trail. DENY and REVIEW decisions are written to a JSONL audit log so you can see what was blocked and why.
Quickstart
pip install clawtex
Claude works out of the box. The Anthropic SDK ships as a core dependency, so a
bare pip install clawtex is enough to run with an Anthropic key.
1. See it work, zero keys, no signup:
clawtex demo
clawtex demo runs entirely on your machine with a mock LLM and an in-memory
store. It shows three things: the agent responds to a prompt, the real Warden
denies a dangerous action (file.delete) before it runs, and a seeded fact is
recalled. No API key, no network. (Tested in tests/test_demo.py.)
2. Wire the real thing:
clawtex init # interactive wizard: LLM key, EngramPort key, channels
clawtex start # start your governed agent (CLI by default)
clawtex init writes a .clawtex.toml, writes a .env for compatibility, and
copies a starter SOUL.md plus a starter skill into your working directory. The
runtime reads .clawtex.toml automatically on startup.
The wizard asks you to paste an EngramPort key. It does not auto-provision a brain for you. You mint the key at the dashboard (see below) and paste it in. You can press Enter to skip and add it later; the agent then runs without persistent memory until you do.
3. Confirm your config:
clawtex check
clawtex check reports your Warden policy, whether your keys are set, loaded
skills, registered tools and their source modules, and any skill manifest
violations. It exits non-zero if a tool comes from an untrusted source or a skill
manifest is inconsistent, so you can use it in CI.
Getting your keys
You need two keys to go live: an LLM key and an EngramPort memory key.
Channel tokens are optional. To try first without either, run clawtex demo.
EngramPort API key (your agent's memory)
- Go to engramport.com.
- Sign up and create a brain. There is a free tier; check the dashboard for current limits.
- Copy the key. It starts with
ek_live_.
This gives your agent a namespace-isolated memory on the EngramPort API
(https://api.engramport.com). EngramPort is also usable standalone over the
same API and MCP server; ClawTex is one way to drive it.
LLM key, pick one
Anthropic (Claude), recommended
- Go to console.anthropic.com.
- Create an API key under API Keys.
- Copy it. It starts with
sk-ant-.
OpenAI
- Go to platform.openai.com.
- Create a secret key under API Keys.
- Copy it. It starts with
sk-.
OpenAI support is an optional extra: pip install 'clawtex[openai]'.
Channel token, optional, pick one or both
Telegram
- Open Telegram, message @BotFather, send
/newbot. - Follow the prompts to name your bot.
- Copy the token (looks like
7812345678:AAH...). - Message your bot once before starting ClawTex.
Slack
- Go to api.slack.com/apps, create an app from scratch.
- Under OAuth & Permissions, add bot scopes:
chat:write,im:write,channels:read,users:read. - Under Socket Mode, enable it and create an app-level token
(
SLACK_APP_TOKEN, starts withxapp-). - Install the app and copy the Bot User OAuth Token (
xoxb-). - Invite the bot to a channel.
Minimum to go live: an EngramPort key (
ek_live_…) plus one LLM key. Without a channel token, ClawTex runs in CLI mode. Or skip keys entirely and runclawtex demo.
Architecture
┌───────────────────────────────────────────────────────────────┐
│ ClawTex Agent │
│ │
│ Channel │
│ ┌──────────┐ │
│ │ Telegram │ │
│ │ Slack │──message──▶ handle_message() │
│ │ CLI │ │
│ └──────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Agent loop (per message) │ │
│ │ │ │
│ │ 1. classify intent (INSTANT / LIGHT / STANDARD/DEEP) │ │
│ │ 2. recall memories (EngramPort or pgvector) │ │
│ │ 3. build prompt (SOUL.md + memory + skills) │ │
│ │ 4. call LLM (Anthropic / OpenAI) │ │
│ │ 5. for each tool call: │ │
│ │ breathe() ─▶ Warden.check_async(ctx) │ │
│ │ ├─ ALLOW ─▶ execute │ │
│ │ ├─ DENY ─▶ BlockedByWarden │ │
│ │ └─ REVIEW ─▶ approval callback, │ │
│ │ else block │ │
│ │ 6. redact + ingest the exchange (MemoryContext) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ Four governance layers (see docs/GOVERNANCE.md): │
│ Warden · tool registry trust · skill manifests · redactor │
└───────────────────────────────────────────────────────────────┘
EngramPort is also exposed as a standalone MCP server for editors like Claude
Code, started with python -m clawtex.mcp (requires pip install 'clawtex[mcp]').
That is a separate entry point, not a chat channel in the agent loop above.
See docs/GOVERNANCE.md for the layer-by-layer walkthrough.
Configuration
clawtex init writes a .clawtex.toml, and the runtime loads it automatically
at startup. Precedence is real environment variables > ./.clawtex.toml >
~/.clawtex.toml > defaults. You can also set any variable below directly in
the environment or a .env file; they map 1:1 onto the TOML keys.
Core
| Variable | Required | Description |
|---|---|---|
CLAWTEX_NAME |
Yes | Agent name / memory namespace |
CLAWTEX_SOUL |
No | Path to SOUL.md (default: ./SOUL.md) |
LLM_PROVIDER |
Yes | anthropic or openai |
ANTHROPIC_API_KEY |
If anthropic | Your Anthropic API key |
OPENAI_API_KEY |
If openai | Your OpenAI API key |
ANTHROPIC_MODEL |
No | Override the Claude model (default: claude-sonnet-4-5-20250929) |
OPENAI_MODEL |
No | Override the OpenAI model (default: gpt-4o) |
Per-tier Anthropic overrides (ANTHROPIC_MODEL_LIGHT, _STANDARD, _DEEP) are
also read if set.
Memory
| Variable | Required | Description |
|---|---|---|
MEMORY_BACKEND |
No | engramport (default) or pgvector |
CLAWTEX_ENGRAMPORT_URL |
No | EngramPort base URL (default: https://api.engramport.com) |
CLAWTEX_ENGRAMPORT_KEY |
Yes (EngramPort) | Your EngramPort API key (ek_live_…) |
CLAWTEX_ENGRAMPORT_GCLOUD_ACCOUNT |
No | gcloud identity token path (self-hosted Cloud Run) |
DREAM_INTERVAL_HOURS |
No | Memory maintenance interval (default: 6) |
CLAWTEX_REDACT_DISABLED |
No | 1 to turn off redaction (debug only) |
CLAWTEX_REDACT_PII |
No | 1 to also redact emails and long digit runs |
CLAWTEX_REDACT_EXTRA |
No | Additional regex for your own secret shapes |
The older CLAWTEX_EIDETIC_* names are still accepted as legacy aliases.
Governance
| Variable | Required | Description |
|---|---|---|
WARDEN_POLICY |
No | Path to a policy YAML (default: bundled default.yaml) |
WARDEN_MODE |
No | enforce (default), audit, or strict |
CLAWTEX_TRUSTED_TOOL_MODULES |
No | Module prefixes allowed to register tools. Prefix with strict: to hard-reject others. Set this in prod. |
Channels
| Variable | Required | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Optional | Telegram bot token |
SLACK_BOT_TOKEN |
Optional | Slack bot token |
SLACK_APP_TOKEN |
Optional | Slack app-level token (Socket Mode) |
Governance
Governance in ClawTex is four independent layers, not one policy engine. Each layer closes a different class of failure.
| Layer | What it does |
|---|---|
| Warden | Evaluates every tool call against a policy. ALLOW / REVIEW / DENY. |
| Tool registry trust | Flags or rejects tools registered from untrusted Python modules. |
| Skill manifests | Audits that skills only drive their declared action_types. |
| Memory redactor | Scrubs known secret shapes and optional PII before any memory ingest. |
Full walkthrough of every knob: docs/GOVERNANCE.md
The bundled default policy
The policy that ships in clawtex/governance/policies/default.yaml:
version: 2
default: REVIEW # unmatched action_types pause before acting
rules:
- action: "file.delete"
decision: DENY
- action: "exec"
decision: REVIEW
- action: "email.delete"
decision: REVIEW
- action: "email.send"
decision: REVIEW
- action: "http.post"
decision: REVIEW
- action: "web.search"
decision: ALLOW
- action: "file.read"
decision: ALLOW
- action: "http.get"
decision: ALLOW
# see the file for the full list
default: REVIEW is the BB4C posture: anything the policy author didn't
anticipate pauses rather than proceeds. A deployment that wants ALLOW-by-default
must set it explicitly. The bundled policy denies file.delete and reviews
exec; if you want hard denies for db.drop / db.truncate, add them in your
own policy file (there is a starter in docs/GOVERNANCE.md).
Context-aware conditions
Rules can gate on the tool call's arguments (path, URL host, recipient, regex):
rules:
- action: "file.write"
decision: ALLOW
when:
input.path:
glob: ["/tmp/**", "./workspace/**"]
- action: "file.write"
decision: REVIEW
- action: "http.post"
decision: ALLOW
when:
input.url:
host_suffix: ["an2b.com"] # api.an2b.com passes, evilan2b.com does not
- action: "http.post"
decision: REVIEW
Operators: equals, not_equals, in, not_in, glob, regex, host_in,
host_suffix. Multiple operators in one when AND together. An unknown
operator fails closed (the rule does not match).
Warden modes
| Mode | Behavior |
|---|---|
enforce (default) |
DENY blocks. REVIEW pauses for the approval callback, or blocks if none is set. |
audit |
Log but allow, except exec, exec.*, file.delete, email.delete, db.drop, db.truncate, which always enforce (NEVER_AUDIT_BYPASS). |
strict |
REVIEW is treated as DENY (no human in the loop). |
Tool registry trust (CLAWTEX_TRUSTED_TOOL_MODULES)
Any Python package that imports clawtex.tools.registry can register a tool.
Without a trust setting, a dependency could register exec under
action_type="web.search" and get past Warden. Set the allowed module prefixes:
# Permissive: warn on untrusted sources (dev)
CLAWTEX_TRUSTED_TOOL_MODULES=clawtex.tools,my_company_tools
# Hard-reject untrusted sources at registration time (prod)
CLAWTEX_TRUSTED_TOOL_MODULES=strict:clawtex.tools,my_company_tools
clawtex check lists each tool's source module and flags untrusted ones.
Memory redactor
Vector memory is durable. A user pasting an API key should not become a
permanent, recallable artifact. clawtex.memory.redactor runs over every
exchange before ingest:
# Input
"my key is sk-ant-AAAABBBBCCCCDDDDEEEEFFFF1234"
# What is written to memory
"my key is [REDACTED:ANTHROPIC_KEY]"
Built-in shapes: Anthropic, OpenAI, EngramPort keys (ek_live_ / ek_test_ /
ek_bot_), GitHub tokens, AWS access and secret keys, Google API keys, Slack
tokens, JWTs, Bearer tokens, and PEM blocks.
CLAWTEX_REDACT_PII=1 # also scrub emails + long digit runs
CLAWTEX_REDACT_EXTRA=INT-[A-Z0-9]{8} # your own secret shapes
CLAWTEX_REDACT_DISABLED=1 # debug only, do not set in prod
The redactor matches the secret shapes listed above. It does not redact arbitrary file paths or free-form sensitive content, so do not point the agent at sensitive or regulated data and assume redaction will catch it.
The breathe() method
# clawtex/agent.py
async def breathe(self, tool: ToolDefinition, tool_input: dict) -> Decision:
"""Pause and consult the Warden before executing any tool call. BB4C."""
context = {"tool": tool.name, "input": tool_input, "agent": self.name}
decision = await self.warden.check_async(tool.action_type, context)
if self.warden.enforce_decision(tool.action_type, decision):
raise BlockedByWarden(tool.action_type, tool.name)
return decision
warden.enforce_decision() is the single "should I block?" answer. It encodes
mode and NEVER_AUDIT_BYPASS in one place so callers can't drift.
Memory
ClawTex runs on two interchangeable memory backends. Pick one per deployment.
| Backend | Use when | Set MEMORY_BACKEND= |
|---|---|---|
| EngramPort (default) | You want managed vector + graph memory with grooming and consolidation | engramport |
| pgvector | You run your own Postgres (e.g. Supabase) and want schema isolation per tenant | pgvector |
Both present the same MemoryContext interface (build(), record_exchange(),
ingest_fact()), so switching backends is an env-var change, not a code change.
The typed-write and graph filter features (typed nodes, dedup keys, recall
filters) are supported on EngramPort; on pgvector they degrade gracefully to
no-ops rather than erroring.
How it works
# Before each response: recall what is relevant
context = await memory.build(query=user_message)
# After each exchange: record it (redacted first)
await memory.record_exchange(user_message=msg, assistant_reply=reply)
# Periodically: groom then consolidate (EngramPort)
await memory.trigger_groom()
await memory.trigger_dream()
Typed graph memory
Raw exchanges are stored as memory nodes. Two typed writes give the graph
higher-signal material:
ingest_principle()stores a durable lesson or rule as aprinciplenode.ingest_insight()stores a synthesized observation as aninsightnode.
A dedup_key makes a write idempotent: re-ingesting the same key updates the
node in place instead of piling up duplicates. Identity is namespace + dedup_key.
record_user_profile() uses this to keep one profile row per user.
Recall can be filtered by node type, similarity floor, source tag, date window, and whether to include graph-linked neighbours. The intent router drives these per turn so deep analysis pulls a wider, graph-linked slice while cheap turns stay tight.
Redaction before ingest
Every user_message and assistant_reply passes through the redactor before
being written. See the Memory redactor section for the shapes
it covers and its limits.
Startup recall
agent.start() recalls recent context from the backend and injects it into the
system prompt before the first message is handled, so a restarted agent comes up
with its prior context instead of a blank slate.
agent = ClawTexAgent(name="MyAgent")
await agent.start() # recalls recent context, injects it into the system prompt
This is fail-open: if the backend is unreachable, the agent continues without startup context rather than blocking startup.
Memory maintenance
ClawTex runs a background loop every DREAM_INTERVAL_HOURS (default 6) that
grooms (auto-links related memories) and then consolidates (synthesizes
higher-order insights) on the EngramPort backend. Each step is best-effort and
non-fatal: a maintenance failure never takes down the request path.
Skills (OpenClaw-compatible)
ClawTex uses the same SKILL.md format as OpenClaw, and adds an optional
governance declaration (declared_action_types).
Full authoring guide: docs/SKILLS.md
Installing skills via clawhub
Skill install and search are a thin wrapper around the clawhub CLI. Install it
first:
npm install -g clawhub
clawtex skill install weather
clawtex skill install github/issues
clawtex skill search summarize
clawtex skill list
clawtex skill update # all skills
clawtex skill update weather # one skill
clawtex skill list works without clawhub (it scans your skills directory).
install, search, and update require the clawhub binary on your PATH and
fail with a clear message if it is missing.
How skills work
At startup, ClawTex scans CLAWTEX_SKILLS_DIR (default ./skills) recursively
for SKILL.md files, parses their YAML frontmatter, and injects an
<available_skills> block into the system prompt.
Per message, it runs keyword overlap between the message and each skill's
description, tags, and name. If a skill matches, its full SKILL.md body is
injected as <active_skill>. The LLM then decides which tools to call, and each
tool call is gated by the Warden as usual. Skills are prompt-level: they do not
register tools or grant tool access.
Writing a skill
skills/
└── my-skill/
└── SKILL.md
---
name: my-skill
display_name: My Skill
description: What this skill does in one line.
version: 1.0.0
tags: [keyword1, keyword2]
tools: [web_search, file_read]
declared_action_types: [web.search, file.read]
---
## Instructions
Describe what the agent should do when this skill is active.
---
A ClawTex skill · BB4C · AN2B LLC
description and tags drive keyword matching. declared_action_types lets
clawtex check audit that the skill's referenced tools only drive the
action_types you declared. If a skill lists tools: [exec] but declares only
web.search, the audit flags it. The check is opt-in: skills without the field
are skipped. Glob patterns like file.* are supported.
ClawTex vs OpenClaw, skill differences
| Feature | OpenClaw | ClawTex |
|---|---|---|
| SKILL.md format | yes | yes (identical) |
| clawhub install | yes | yes |
| Skill injection | system prompt | system prompt |
| Per-message matching | yes | yes |
declared_action_types audit |
no | yes |
| EngramPort / pgvector memory | no | yes |
| Warden governance | no | yes |
| Tool registry trust model | no | yes |
| Secret/PII redaction | no | yes |
Adding tools
Tools are async Python functions registered with the tool registry:
# my_company_tools/my_tool.py
from clawtex.tools.registry import registry
@registry.register(
name="my_tool",
action_type="my.tool", # must map to a Warden policy rule
description="Does something useful.",
parameters={...}, # JSON Schema
)
async def my_tool(param1: str, **_) -> dict:
return {"result": "..."}
Trust model
Every registered tool captures its source module via call-stack inspection. If
you ship tools from a non-core package, add its module prefix to
CLAWTEX_TRUSTED_TOOL_MODULES:
CLAWTEX_TRUSTED_TOOL_MODULES=clawtex.tools,my_company_tools
clawtex check flags any tool registered from an untrusted source and exits
non-zero. See
docs/GOVERNANCE.md for
the full threat model.
Testing
pytest tests/ -v
# Governance tests
pytest tests/test_governance.py -v
# With coverage
pytest tests/ --cov=clawtex --cov-report=term-missing
The meta inbox test
The signature governance test simulates 200 email deletion requests. Every one must return DENY or REVIEW, never ALLOW.
def test_200_email_deletes_never_allow(self) -> None:
warden = make_warden()
for i in range(200):
decision = warden.check("email.delete", {"email_id": f"msg_{i:04d}"})
assert decision != "ALLOW"
Docker
docker-compose up -d
docker-compose logs -f clawtex
License
MIT. See LICENSE.
About
ClawTex is built by DeVere Cooley at AN2B LLC.
BB4C. Breath Before Code.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clawtex-3.1.0.tar.gz.
File metadata
- Download URL: clawtex-3.1.0.tar.gz
- Upload date:
- Size: 135.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93e42303613855e262fd58e4a1f8b8259accd5c6675a567a0c7ae8363e127f37
|
|
| MD5 |
cd4727f098c2692fe64b62c668d130c4
|
|
| BLAKE2b-256 |
f1502262f9d1292957179671c6965e881cfb6af7439c55ca8440775f7ec23cd1
|
Provenance
The following attestation bundles were made for clawtex-3.1.0.tar.gz:
Publisher:
publish.yml on jcools1977/clawtex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clawtex-3.1.0.tar.gz -
Subject digest:
93e42303613855e262fd58e4a1f8b8259accd5c6675a567a0c7ae8363e127f37 - Sigstore transparency entry: 1910749266
- Sigstore integration time:
-
Permalink:
jcools1977/clawtex@9f8271152193c5b28d75a94283511299ca54db4e -
Branch / Tag:
refs/tags/v3.1.0 - Owner: https://github.com/jcools1977
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9f8271152193c5b28d75a94283511299ca54db4e -
Trigger Event:
push
-
Statement type:
File details
Details for the file clawtex-3.1.0-py3-none-any.whl.
File metadata
- Download URL: clawtex-3.1.0-py3-none-any.whl
- Upload date:
- Size: 150.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
759b40380aa2d3d710846c31009f0464b8cdce097d43d5954f125381023c0f10
|
|
| MD5 |
35292f0855eda0c4e975a38f5c85927c
|
|
| BLAKE2b-256 |
da7a75b92ec25355f160ab4a8eda8f205ea8f31649b518ea021e0cc991d3bd06
|
Provenance
The following attestation bundles were made for clawtex-3.1.0-py3-none-any.whl:
Publisher:
publish.yml on jcools1977/clawtex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clawtex-3.1.0-py3-none-any.whl -
Subject digest:
759b40380aa2d3d710846c31009f0464b8cdce097d43d5954f125381023c0f10 - Sigstore transparency entry: 1910749348
- Sigstore integration time:
-
Permalink:
jcools1977/clawtex@9f8271152193c5b28d75a94283511299ca54db4e -
Branch / Tag:
refs/tags/v3.1.0 - Owner: https://github.com/jcools1977
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9f8271152193c5b28d75a94283511299ca54db4e -
Trigger Event:
push
-
Statement type: