Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
455a1f1
0.2.1: structured handoff records + huggingface pip extra
Arrmlet Jun 6, 2026
d8a9fa7
ci: add ruff format check; format codebase; add handoff tests
Arrmlet Jun 6, 2026
315cc55
hf: implement ensure_bucket() via HfApi.create_bucket, private by def…
Arrmlet Jun 9, 2026
9f0659e
init: add --private/--public flag (default: private)
Arrmlet Jun 9, 2026
9f581e9
init: report real HF bucket visibility from bucket_info()
Arrmlet Jun 9, 2026
c6ec0df
init: warn prominently when an existing HF bucket is public
Arrmlet Jun 9, 2026
976224f
hf: actionable error when a write hits a missing bucket
Arrmlet Jun 9, 2026
a11559e
hf: don't swallow auth errors as False in exists()
Arrmlet Jun 9, 2026
55095d2
messages: unique keys per send; inbox sorted by sent_at
Arrmlet Jun 9, 2026
2c572b0
steps: complete enforces claim ownership; --force to override
Arrmlet Jun 9, 2026
de0cb61
steps: tolerate the claim/status crash window in readers
Arrmlet Jun 9, 2026
b6d9d6d
steps: wait-for fails fast on blocked steps
Arrmlet Jun 9, 2026
453d8d2
deps: drop unused httpx and pydantic
Arrmlet Jun 9, 2026
7e82b5f
tests: coordination correctness + messaging + HF onboarding
Arrmlet Jun 9, 2026
8c3a464
ci: consolidate workflows into ci.yml (ruff check + format + pytest)
Arrmlet Jun 9, 2026
a6db54b
docs: refresh CLAUDE.md known gaps after the correctness pass
Arrmlet Jun 9, 2026
89e3fa2
README: document session mirroring properly
Arrmlet Jun 9, 2026
41a2575
README: quick start takes credentials from AWS env vars
Arrmlet Jun 9, 2026
0f5734d
README: document HF bucket privacy behavior
Arrmlet Jun 9, 2026
70499ca
README: add 'Why not X?' and 'Status & limitations' sections
Arrmlet Jun 9, 2026
1aa52ea
README: add minimal Python API snippet
Arrmlet Jun 9, 2026
83cc0e5
dev env: MinIO-only compose; .env.example matches reality
Arrmlet Jun 9, 2026
82ba586
Merge main (squash of PR #6 — content already on this branch as 455a1…
Arrmlet Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 14 additions & 30 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
# Tracecraft Configuration
# Tracecraft environment variables — only what the CLI actually reads.

# SeaweedFS Configuration
TRACECRAFT_SEAWEEDFS_S3_ENDPOINT=localhost:8333
TRACECRAFT_SEAWEEDFS_ACCESS_KEY=admin
TRACECRAFT_SEAWEEDFS_SECRET_KEY=admin_secret_key
TRACECRAFT_SEAWEEDFS_USE_SSL=false
# S3 backend credentials (read by `tracecraft init`; match docker-compose.dev.yml)
AWS_ACCESS_KEY_ID=admin
AWS_SECRET_ACCESS_KEY=admin123456

# Security Configuration
TRACECRAFT_SECURITY_ENCRYPTION_ENABLED=true
TRACECRAFT_SECURITY_JWT_SECRET=your-jwt-secret-here
# HuggingFace backend token (read by `tracecraft init --backend hf`)
# HF_TOKEN=hf_...

# Storage Configuration
TRACECRAFT_STORAGE_BUCKET_NAME=tracecraft-data
TRACECRAFT_STORAGE_RETENTION_DAYS=90
# Override the agent identity per shell/process (lets several agents share one
# directory and .tracecraft.json)
# TRACECRAFT_AGENT=designer

# UI Configuration
TRACECRAFT_UI_HOST=0.0.0.0
TRACECRAFT_UI_PORT=8000
TRACECRAFT_UI_AUTH_REQUIRED=false

# Monitoring Configuration
TRACECRAFT_MONITORING_ENABLED=true

# Database Configuration
TRACECRAFT_DATABASE_HOST=localhost
TRACECRAFT_DATABASE_PORT=5432
TRACECRAFT_DATABASE_DATABASE=tracecraft
TRACECRAFT_DATABASE_USER=tracecraft
TRACECRAFT_DATABASE_PASSWORD=tracecraft

# Redis Configuration
TRACECRAFT_REDIS_HOST=localhost
TRACECRAFT_REDIS_PORT=6379
# Session-mirror harness location overrides (only if your harness lives in a
# non-default path)
# OPENCLAW_STATE_DIR=
# OPENCLAW_HOME=
# HERMES_HOME=
11 changes: 7 additions & 4 deletions .github/workflows/test.yml → .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: tests
name: ci

on:
push:
Expand All @@ -7,7 +7,7 @@ on:
branches: [main]

jobs:
format:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -18,12 +18,15 @@ jobs:
python-version: "3.12"
cache: pip

- name: Install ruff
- name: Install package + dev extras
working-directory: sdk
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Lint (ruff check)
run: ruff check sdk/

- name: Check formatting (ruff format)
run: ruff format --check sdk/

Expand All @@ -32,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.12"]

steps:
- uses: actions/checkout@v4
Expand Down
16 changes: 10 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ pivot lives in `plans/server-archive/` for reference only — nothing in the SDK
<bucket>/<project>/
agents/<agent_id>.json ← agent registration + heartbeat
memory/<dotted.key>.json ← shared key-value state
messages/<recipient>/<ts>_<from>.json ← per-agent mailbox
messages/_broadcast/<ts>_<from>.json ← broadcast
messages/<recipient>/<ts_ns>_<from>_<uuid8>.json ← per-agent mailbox
messages/_broadcast/<ts_ns>_<from>_<uuid8>.json ← broadcast
steps/<step_id>/claim.json ← atomic claim (If-None-Match=*)
steps/<step_id>/status.json ← pending / in_progress / complete
steps/<step_id>/handoff.json ← note + from_agent for next agent
Expand All @@ -56,13 +56,17 @@ pivot lives in `plans/server-archive/` for reference only — nothing in the SDK
many isolated projects.
- **No server, no daemon**: each CLI call is stateless; state lives on the bucket.
- **No vendor lock-in**: AWS, R2, MinIO, B2, Wasabi, HuggingFace all work today.
- **Claim/status crash-window invariant**: `claim.json` (atomic) and `status.json` are
two separate writes; a crash between them leaves a claim with no status. Readers MUST
treat "claim.json exists, status.json missing" as `in_progress` by the claiming agent —
the claim is the authoritative write (`step-status` and `wait-for` implement this via
`_effective_status` in `cli/steps.py`).

## Known gaps (May 2026)
## Known gaps (June 2026)

- No TTL on claims (a crashed claim-holder keeps the lock forever) — Tier 1 work.
- No TTL on claims (a crashed claim-holder keeps the lock forever; `complete --force`
is the manual escape hatch) — Tier 1 work.
- Heartbeat is written at `init` only, never refreshed — Tier 1 work.
- Messages keyed by `<ts>_<sender>.json` can collide same-second — Tier 1 work.
- No tests in `sdk/tests/` — Tier 1 work.

## Building

Expand Down
63 changes: 53 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![PyPI](https://img.shields.io/pypi/v/tracecraft-ai)](https://pypi.org/project/tracecraft-ai/)
[![Python](https://img.shields.io/pypi/pyversions/tracecraft-ai)](https://pypi.org/project/tracecraft-ai/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://github.com/Arrmlet/tracecraft/actions/workflows/test.yml/badge.svg)](https://github.com/Arrmlet/tracecraft/actions/workflows/test.yml)
[![CI](https://github.com/Arrmlet/tracecraft/actions/workflows/ci.yml/badge.svg)](https://github.com/Arrmlet/tracecraft/actions/workflows/ci.yml)

**Tracecraft is a CLI coordination layer for multi-agent AI systems** — shared **memory**, a **mailbox**, atomic task **claims**, **handoffs**, and **artifacts**, plus mirrored **session transcripts**, all stored as plain JSON in any **S3** or **HuggingFace** bucket. No server. No database. No SDK lock-in.

Expand All @@ -29,20 +29,25 @@ docker run -d -p 9000:9000 \
minio/minio server /data
```

Register two agents against the same project:
(From a checkout, `docker compose -f docker-compose.dev.yml up -d` does the same and adds the MinIO console on `:9001`.)

Register two agents against the same project. Credentials come from the standard AWS env vars, so they never land in your shell history:

```bash
export AWS_ACCESS_KEY_ID=admin
export AWS_SECRET_ACCESS_KEY=admin123456

# Terminal 1
tracecraft init --project demo --agent designer \
--endpoint http://localhost:9000 --bucket tracecraft \
--access-key admin --secret-key admin123456
--endpoint http://localhost:9000 --bucket tracecraft

# Terminal 2 — same flags, --agent developer
tracecraft init --project demo --agent developer \
--endpoint http://localhost:9000 --bucket tracecraft \
--access-key admin --secret-key admin123456
--endpoint http://localhost:9000 --bucket tracecraft
```

`init` writes the config to `.tracecraft.json` with mode `600` and auto-adds it to `.gitignore` when you're in a git repo.

Now the core move — **two agents cannot grab the same work**, with no lock service and no server to run:

```console
Expand Down Expand Up @@ -96,16 +101,39 @@ tracecraft send _broadcast "v1 cut at 3pm, wrap your tasks"

---

## Coordination + reasoning in one bucket
## Why not LangGraph / Redis / message queues?

- **Frameworks (LangGraph, CrewAI, AutoGen)** orchestrate agents *inside one process*. Tracecraft coordinates *any* processes across machines — different harnesses, different clouds, different teams — through storage they already have.
- **Redis / Postgres / a queue** means operating a server: provisioning, auth, uptime, backups. A bucket is zero infra, and every state change is a browsable JSON file — you get an audit trail for free just by opening the bucket.
- **A2A / MCP** are live wire protocols between *running* agents. Tracecraft is durable state for agents that aren't running at the same time — one agent finishes Tuesday, the next picks up the handoff Wednesday.

## Status & limitations

Tracecraft is **pre-alpha**. Honest sharp edges, as of now:

- **No TTL on claims** — a crashed claim-holder keeps the lock until someone runs `complete --force`.
- **Heartbeat isn't refreshed** — `agents` shows who registered, not who's alive right now.
- **HF claims are best-effort** — HuggingFace Buckets have no conditional write, so atomic claims need an S3-compatible backend.

Open issues and roadmap → [github.com/Arrmlet/tracecraft/issues](https://github.com/Arrmlet/tracecraft/issues)

---

## Session mirroring

Most coordination tools store the *events* — who claimed what, who messaged whom. Tracecraft stores those **and** each agent's full reasoning, by mirroring coding-agent session transcripts into the same bucket. When a run goes sideways, one `tracecraft session show` gives you the handoffs **and** the chain of thought behind them — same place, same JSON, no second system to wire up.

```bash
tracecraft session mirror --harness claude-code # tail this session into the bucket
tracecraft session show <id> --tail 50 # read coordination + reasoning together
tracecraft session mirror --harness claude-code # upload this session's new bytes
tracecraft session list # browse mirrored sessions
tracecraft session show <id> --tail 50 # replay: meta + last N transcript lines
tracecraft session stop <id> # clear local cursor, mark session ended
```

Works with **Claude Code, Codex, OpenClaw, and Hermes**. Source transcripts are never modified; secret-shape redaction (AWS / Anthropic / OpenAI / HF / GitHub / Slack token patterns) is on by default and counted in metadata.
- **Four harnesses** — `claude-code`, `codex`, `openclaw`, `hermes`. Anything else can mirror by writing JSONL to the same layout.
- **Incremental cursor uploads** — `mirror` keeps a per-session byte offset and uploads only what's new as numbered parts, so re-running it from a cron or hook is safe and cheap; a run with nothing new is a no-op. The part sequence is derived from the bucket, so it even survives losing the local state file.
- **Redaction on by default** — AWS / Anthropic / OpenAI / HF / GitHub / Slack token shapes are scrubbed before upload, with per-pattern match counts recorded in the session's `meta.json` (pass `--no-redact` to opt out). Source transcripts are never modified.
- **Replay** — `session show <id> --tail N` concatenates the uploaded parts and prints the last N transcript lines next to the session metadata.

Harness matrix, storage formats, and redaction details → **[docs/session-mirror.md](docs/session-mirror.md)**

Expand Down Expand Up @@ -145,6 +173,8 @@ Bring your own bucket — no vendor lock-in:
| Backblaze B2 / Wasabi | S3-compatible endpoint | |
| HuggingFace Buckets | `--backend hf --bucket user/name` | browsable on the Hub; `pip install tracecraft-ai[huggingface]` |

**HuggingFace privacy:** `init` creates the bucket **private by default** (pass `--public` to opt out) and prints the bucket's *actual* visibility, read back from the Hub — e.g. `Backend: HuggingFace Buckets Bucket: user/x (private)`. If the bucket already exists as public and you didn't ask for that, init warns loudly: coordination data and mirrored transcripts would be publicly visible. Visibility can't be flipped after creation (`huggingface_hub` has no `update_bucket`) — the only way to change it is delete + recreate.

---

## Use cases
Expand Down Expand Up @@ -199,6 +229,19 @@ TRACECRAFT_AGENT=developer tracecraft inbox

---

## Python API

The CLI is the stable interface; for code that wants direct bucket access, the store factory is the escape hatch:

```python
from tracecraft.store import get_store

store, cfg = get_store() # reads .tracecraft.json like the CLI does
store.put_json("memory/build/status.json", {"value": "passing", "set_by": cfg["agent_id"]})
```

---

## More

- [docs/session-mirror.md](docs/session-mirror.md) — session mirroring: harnesses, formats, redaction
Expand Down
50 changes: 11 additions & 39 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,17 @@
version: "3.8"

# Local dev backend: a single MinIO bucket is all tracecraft needs.
# Console at http://localhost:9001 to watch agents coordinate live.
services:
postgres:
image: postgres:16
minio:
image: minio/minio
command: server /data --console-address ":9001"
ports:
- "5432:5432"
- "9000:9000"
- "9001:9001"
environment:
POSTGRES_DB: tracecraft
POSTGRES_USER: tracecraft
POSTGRES_PASSWORD: tracecraft
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: admin123456
volumes:
- postgres_data:/var/lib/postgresql/data

redis:
image: redis:7-alpine
ports:
- "6379:6379"

seaweed-master:
image: chrislusf/seaweedfs
command: master -ip=seaweed-master -port=9333
ports:
- "9333:9333"

seaweed-volume:
image: chrislusf/seaweedfs
command: volume -mserver=seaweed-master:9333 -port=8080 -ip=seaweed-volume
ports:
- "8080:8080"
depends_on:
- seaweed-master

seaweed-filer:
image: chrislusf/seaweedfs
command: filer -master=seaweed-master:9333 -port=8888 -s3 -s3.port=8333
ports:
- "8888:8888"
- "8333:8333"
depends_on:
- seaweed-master
- seaweed-volume
- minio_data:/data

volumes:
postgres_data:
minio_data:
2 changes: 0 additions & 2 deletions sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ classifiers = [
]
dependencies = [
"click>=8.1.0",
"httpx>=0.25.0",
"pydantic>=2.5.0",
"boto3>=1.28.0",
]

Expand Down
Loading
Loading