Metadata-Version: 2.4
Name: clawtex
Version: 3.0.0
Summary: Governed agent platform built on EngramPort GraphRAG memory. BB4C — Breath Before Code.
License: MIT
License-File: LICENSE
Keywords: ai,agent,governance,memory,llm,engramport,bb4c
Author: DeVere Cooley
Author-email: j@an2b.com
Requires-Python: >=3.11
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Provides-Extra: all
Provides-Extra: dashboard
Provides-Extra: mcp
Provides-Extra: openai
Provides-Extra: slack
Provides-Extra: telegram
Requires-Dist: anthropic (>=0.34)
Requires-Dist: fastapi (>=0.110) ; extra == "dashboard" or extra == "all"
Requires-Dist: fastmcp (>=2.0) ; extra == "mcp" or extra == "all"
Requires-Dist: httpx (>=0.27)
Requires-Dist: jinja2 (>=3.1) ; extra == "dashboard" or extra == "all"
Requires-Dist: openai (>=1.45) ; extra == "openai" or extra == "all"
Requires-Dist: python-dotenv (>=1.0)
Requires-Dist: python-telegram-bot (>=20.0) ; extra == "telegram" or extra == "all"
Requires-Dist: pyyaml (>=6.0)
Requires-Dist: slack-bolt (>=1.18) ; extra == "slack" or extra == "all"
Requires-Dist: uvicorn (>=0.29) ; extra == "dashboard" or extra == "all"
Project-URL: Homepage, https://engramport.com
Project-URL: Repository, https://github.com/jcools1977/clawtex
Description-Content-Type: text/markdown

# ClawTex

> BB4C. Breath Before Code.
> Built by [DeVere Cooley](https://an2b.com), 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:

1. **Governance on every tool call.** The Warden policy engine evaluates each
   tool call and returns ALLOW, DENY, or REVIEW before the call runs.
2. **Typed graph memory.** Memory lives in [EngramPort](https://engramport.com),
   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.
3. **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

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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)

1. Go to **[engramport.com](https://engramport.com)**.
2. Sign up and create a brain. There is a free tier; check the dashboard for
   current limits.
3. 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**
1. Go to **[console.anthropic.com](https://console.anthropic.com)**.
2. Create an API key under **API Keys**.
3. Copy it. It starts with `sk-ant-`.

**OpenAI**
1. Go to **[platform.openai.com](https://platform.openai.com)**.
2. Create a secret key under **API Keys**.
3. Copy it. It starts with `sk-`.

OpenAI support is an optional extra: `pip install 'clawtex[openai]'`.

### Channel token, optional, pick one or both

**Telegram**
1. Open Telegram, message **@BotFather**, send `/newbot`.
2. Follow the prompts to name your bot.
3. Copy the token (looks like `7812345678:AAH...`).
4. Message your bot once before starting ClawTex.

**Slack**
1. Go to **[api.slack.com/apps](https://api.slack.com/apps)**, create an app
   from scratch.
2. Under **OAuth & Permissions**, add bot scopes: `chat:write`, `im:write`,
   `channels:read`, `users:read`.
3. Under **Socket Mode**, enable it and create an app-level token
   (`SLACK_APP_TOKEN`, starts with `xapp-`).
4. Install the app and copy the **Bot User OAuth Token** (`xoxb-`).
5. 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
> run `clawtex 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`](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`](docs/GOVERNANCE.md)**

### The bundled default policy

The policy that ships in `clawtex/governance/policies/default.yaml`:

```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):

```yaml
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:

```bash
# 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:

```python
# 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.

```bash
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

```python
# 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

```python
# 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 a `principle` node.
- `ingest_insight()` stores a synthesized observation as an `insight` node.

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](#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.

```python
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`](docs/SKILLS.md)**

### Installing skills via clawhub

Skill install and search are a thin wrapper around the `clawhub` CLI. Install it
first:

```bash
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
```

```markdown
---
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:

```python
# 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`:

```bash
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`](docs/GOVERNANCE.md#layer-2-tool-registry-trust-model) for
the full threat model.

---

## Testing

```bash
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.

```python
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

```bash
docker-compose up -d
docker-compose logs -f clawtex
```

---

## License

MIT. See LICENSE.

---

## About

ClawTex is built by [DeVere Cooley](https://an2b.com) at AN2B LLC.

> BB4C. Breath Before Code.

