Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e93817d
Add TypeScript tool registry
May 23, 2026
53c729f
Add demo IRC tool
May 23, 2026
d28dc9f
Fix env-file lookup for CLI secrets
May 23, 2026
5721bc9
Add CLI library tool manifests
May 23, 2026
6403870
Enable agent-created tools
May 23, 2026
756ad03
Store tool results in artifacts
May 23, 2026
e7d07a5
Add Exoclaw scheduled task harness
May 23, 2026
4f58fa9
Default Exoclaw sandboxes to agent scope
May 24, 2026
66ffb88
Add Exoclaw external adapters
May 24, 2026
b38d369
Move Exoclaw adapters to worker modules
May 24, 2026
af856a5
Merge origin/main into adapters
May 24, 2026
ecba793
Add Exoclaw initial prompt file support
May 24, 2026
0343be6
Merge remote-tracking branch 'origin/main' into adapters
May 24, 2026
10587b7
Add Exoclaw Signal adapter setup
May 25, 2026
dd17407
Fix Signal adapter worker shutdown
May 25, 2026
37878b9
Simplify Exoclaw adapters to worker-only
May 25, 2026
9b9205f
Load Exoclaw identity prompt every turn
May 25, 2026
9b1fcbb
Merge remote-tracking branch 'origin/main' into adapters
May 25, 2026
3549919
Resolve Exoclaw merge with tool modules
May 25, 2026
11da2cd
Fix Exoclaw adapter turn handling
May 26, 2026
7918d03
Merge origin/main into adapters
May 26, 2026
7777e98
Update Exoclaw script for new CLI
May 26, 2026
e00d45f
Fix clippy warnings after main merge
May 26, 2026
ff43d45
Simplify Exoclaw adapter and scheduler surface
May 26, 2026
af427ee
Add local Exoclaw profile prompt
May 26, 2026
39c2955
Move Exoclaw scheduler runner into example
May 27, 2026
6583742
Update Exoclaw docs and stop command
May 27, 2026
d3c4d4d
Remove temporary PR cleanup notes
May 27, 2026
17bc650
Organize Signal adapter Mac install notes
May 27, 2026
599aed2
Remove redundant Exoclaw prompt inventory
May 27, 2026
1249dd8
Support for rich context in WhatsApp and Signal
May 30, 2026
bf15a8c
Merge origin/main into adapters
Jun 1, 2026
caab395
Merge origin/main into adapters
Jun 2, 2026
50fec71
Fix adapter merge fallout.
Jun 2, 2026
45303da
Fix tool runtime test mock.
Jun 2, 2026
a56dbaf
Add Discord adapter.
Jun 2, 2026
4d148ba
Enable Discord rich attachments.
Jun 2, 2026
d7cd490
discord adapter: optional allowBots config (#42)
akrentsel Jun 3, 2026
8ba4da9
Fix Discord allowBots schema.
Jun 3, 2026
bcf4147
Merge main and fix CI failures
ankrgyl Jun 3, 2026
c2eae4f
Update adapter prompts and tool setup notes
Jun 3, 2026
30943f3
fixes
ankrgyl Jun 4, 2026
4e0bd83
fix
ankrgyl Jun 4, 2026
d4df060
Merge origin/adapters
Jun 4, 2026
80d5538
Improve adapter wakeup delivery
Jun 4, 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
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
[workspace]
members = ["crates/cli", "crates/exoharness", "crates/executor"]
members = [
"crates/cli",
"crates/exoharness",
"crates/executor",
"examples/exoclaw/scheduler-runner",
]
resolver = "2"

[workspace.package]
Expand All @@ -16,7 +21,6 @@ braintrust-sdk-rust = { git = "https://github.com/braintrustdata/braintrust-sdk-
bytes = "1.10.1"
chrono = { version = "0.4.42", features = ["serde"] }
clap = { version = "4.5.48", features = ["derive", "env"] }
flate2 = "1.1.2"
futures = "0.3.31"
object_store = { version = "0.12.5", features = [
"fs",
Expand All @@ -28,8 +32,8 @@ object_store = { version = "0.12.5", features = [
reqwest = { version = "0.12.23", default-features = false, features = [
"json",
"rustls-tls",
"stream",
] }
shell-words = "1.1.0"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
sqlx = { version = "0.8.6", default-features = false, features = [
Expand All @@ -45,9 +49,11 @@ tokio = { version = "1.52.1", features = [
"io-std",
"io-util",
"macros",
"net",
"process",
"rt-multi-thread",
"sync",
"time",
] }
tokio-stream = "0.1.17"
tokio-util = { version = "0.7.18", features = ["compat"] }
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,21 @@ and recursive-language-model harness experiments.
For the coding-agent setup commands, see
[docs/coding-agent-harnesses.md](./docs/coding-agent-harnesses.md).

## Exoclaw Long-Running Harness

Exoclaw is a long-running claw-type agent built on exoharness. It supports
scheduled tasks, and a full adapter system including support for WhatsApp,
Signal, and IRC. See [examples/exoclaw/README.md](./examples/exoclaw/README.md)
for setup, operation, and debugging commands.

## Repository Layout

- `crates`: Rust workspace for the CLI, exoharness substrate, and executors.
- `typescript`: TypeScript harness runtime, model-runtime helpers, and
adapter-specific support code.
- `examples/typescript`: runnable TypeScript harness examples.
- `examples/exoclaw`: long-running TypeScript harness example with scheduled
task and adapter support.
- `containers`: sandbox images used by the coding-agent harness examples.
- `spec`: core architecture and terminology.
- `docs`: design notes for in-progress directions.
Expand Down
289 changes: 289 additions & 0 deletions adapter-arch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
# Adapter Architecture

This document is a review map for the Exoclaw adapter changes. It focuses on the minimal architecture: what owns adapter state, what starts workers, how messages move, and which files to inspect.

## What Adapters Are

Adapters are long-running host-managed connections to external services. They let Exoclaw receive messages from outside the REPL and send explicit replies back out.

The adapter subsystem is intentionally separate from normal tools:

- Tools run during a model turn.
- Adapters run continuously in a background host process.
- Adapter events wake a conversation by creating a normal Exoclaw turn.
- Outbound adapter sends are explicit tool calls, not implicit model output.

## Sources

Adapter records have a `source` describing where the adapter comes from:

Current sources:

- `built_in`: core Exoclaw adapter. IRC is the only built-in adapter.
- `library`: reusable adapter shipped with Exoclaw. Signal and WhatsApp are library adapters backed by shipped workers.

All adapters in this PR are worker adapters: supervised processes using JSONL over stdin/stdout. Protocol-specific code should live under `examples/exoclaw/adapters/<adapter>/`, not in the shared Rust runtime.

## Data Model

Core records live in `crates/executor/src/adapter/types.rs`.

Important types:

- `AdapterRecord`: durable adapter config and status.
- `AdapterConfig::Worker`: worker command, initialization JSON, capabilities, optional state dir, optional secret env vars.
- `AdapterEventRecord`: lightweight event history.
- `AdapterOutboundMessageRecord`: queued outbound messages.

There is no module adapter path in this PR. If agent-authored adapters are added later, they should compile or resolve to the same worker shape.

## Storage

The adapter store is file-backed in `crates/executor/src/adapter/store.rs`.

Default root:

```text
.exo/adapters/
```

Layout:

```text
.exo/adapters/adapters/<adapter-id>.json
.exo/adapters/events/<adapter-id>/<event-id>.json
.exo/adapters/outbox/<adapter-id>/<message-id>.json
.exo/adapters/<adapter-type>/<adapter-id>/...
```

Adapter records and event records stay in the store. Larger, conversation-visible payloads are written as conversation artifacts by the runtime.

## Runtime Ownership

The adapter runner is a host process started by the Exoclaw script:

```text
examples/exoclaw/scripts/exoclaw-repl
```

It starts:

```bash
exo --harness exoclaw adapters run --watch --limit <N>
```

The CLI entry point is:

```text
crates/cli/src/adapters.rs
```

Responsibilities:

- Acquire a lock so only one adapter watch runner is active.
- Dispatch `adapters list`, `adapters run`, `adapters disable`, and `adapters delete`.
- Call the executor adapter runtime.

The watch loop is in:

```text
crates/executor/src/adapter/runtime.rs
```

Responsibilities:

- Poll enabled adapter records.
- Start one supervisor task per enabled adapter.
- Skip adapters that are disabled or not build-ready.
- Restart workers after they exit or error.
- Convert worker events into store records, artifacts, and conversation wakeups.
- Drain the outbox and write outbound commands to workers.

## Worker Protocol

The shared worker protocol is implemented in Rust and mirrored in TypeScript:

```text
crates/executor/src/adapter/worker.rs
examples/exoclaw/adapters/protocol.ts
```

Host to worker:

```json
{ "type": "send_message", "target": "...", "text": "..." }
```

Worker to host:

```json
{"type":"connected","subject":"...","metadata":{}}
{"type":"message","target":"...","sender":"...","text":"...","message_id":"...","metadata":{}}
{"type":"lifecycle","name":"...","metadata":{}}
{"type":"error","message":"..."}
{"type":"disconnected","reason":"..."}
```

Workers receive configuration via environment:

- `EXO_ADAPTER_ID`
- `EXO_ADAPTER_TYPE`
- `EXO_ADAPTER_STATE_DIR`
- `EXO_ADAPTER_CONFIG`
- protocol-specific secret env vars, such as `EXO_IRC_PASSWORD`

## Inbound Flow

1. A worker receives an external message.
2. The worker writes a `message` JSONL event to stdout.
3. `run_worker_loop` parses it.
4. `runtime.rs` writes an inbound artifact into the owning conversation.
5. `runtime.rs` records a store event.
6. `runtime.rs` calls `send_conversation_wakeup`.
7. Exoclaw receives a normal user message containing:
- adapter name
- adapter id
- target
- sender
- message text
- instructions for replying with `send_adapter_message`

The wakeup path is shared with scheduler wakeups:

```text
crates/executor/src/conversation_wakeup.rs
```

## Outbound Flow

1. The model explicitly calls `send_adapter_message`.
2. TypeScript tool definitions pass the request to the host tool runtime.
3. `runtime.rs` writes an outbound artifact into the conversation.
4. `AdapterStore` writes an outbox record.
5. The adapter runner drains the outbox once per second.
6. The host writes a `send_message` JSONL command to the worker stdin.
7. The worker sends through the external protocol.

This avoids short-lived reconnects for every outbound message.

## Tool Integration

Model-facing adapter tools are defined in:

```text
typescript/harness/adapter-tools.ts
```

Tools:

- `create_adapter`
- `list_adapters`
- `disable_adapter`
- `delete_adapter`
- `send_adapter_message`

These tools are registered by the Exoclaw harness:

```text
examples/exoclaw/harness.ts
```

Host-side execution is in:

```text
crates/executor/src/harness_tool.rs
crates/executor/src/adapter/tools.rs
```

The TypeScript layer currently transforms typed user-facing adapter configs into generic worker configs. For example, a Signal config becomes a worker config pointing at:

```text
examples/exoclaw/adapters/signal/worker.ts
```

## Protocol Workers

Protocol-specific code lives under:

```text
examples/exoclaw/adapters/
```

Current workers:

- `irc/worker.ts`: IRC socket, registration, channel join, PING/PONG, PRIVMSG parsing.
- `whatsapp/worker.ts`: Baileys linked-device client, QR pairing, WhatsApp messages.
- `signal/worker.ts`: `signal-cli` linked-device flow, JSON-RPC receive/send.

Each adapter directory also has a local README and setup prompt:

```text
examples/exoclaw/adapters/irc/README.md
examples/exoclaw/adapters/irc/setup-prompt.md
examples/exoclaw/adapters/whatsapp/README.md
examples/exoclaw/adapters/whatsapp/setup-prompt.md
examples/exoclaw/adapters/signal/README.md
examples/exoclaw/adapters/signal/setup-prompt.md
```

## Lifecycle

Adapter lifecycle is owned by the host runner, not by the REPL.

Startup:

- `examples/exoclaw/scripts/exoclaw-repl` starts `exo adapters run --watch` unless `--no-adapters` is set.
- The runner writes `.exo/exoclaw-adapters.pid` and logs to `.exo/exoclaw-adapters.log`.
- The runner starts worker processes for enabled, ready adapters.

Restart:

- Worker exit/error returns from `run_worker_loop`.
- The watch task records the error and retries after a short delay.

Stopping:

- `stop_adapters` in `examples/exoclaw/scripts/exoclaw-repl` kills the runner and worker processes.
- Disabling/deleting adapter records prevents future restarts.

## Files To Inspect For PR Review

Core model and runtime:

- `crates/executor/src/adapter/types.rs`
- `crates/executor/src/adapter/store.rs`
- `crates/executor/src/adapter/runtime.rs`
- `crates/executor/src/adapter/worker.rs`
- `crates/executor/src/adapter/tools.rs`
- `crates/cli/src/adapters.rs`

TypeScript tool surface:

- `typescript/harness/adapter-tools.ts`
- `typescript/harness/index.test.ts`
- `examples/exoclaw/harness.ts`

Protocol-specific workers:

- `examples/exoclaw/adapters/protocol.ts`
- `examples/exoclaw/adapters/irc/worker.ts`
- `examples/exoclaw/adapters/whatsapp/worker.ts`
- `examples/exoclaw/adapters/signal/worker.ts`

Script and docs:

- `examples/exoclaw/scripts/exoclaw-repl`
- `examples/exoclaw/README.md`
- `examples/exoclaw/adapter-architecture.md`
- `examples/exoclaw/adapters/*/README.md`
- `examples/exoclaw/adapters/*/setup-prompt.md`

## Minimality Notes

The intended split is:

- Rust owns durable records, lifecycle supervision, outbox, artifacts, and conversation wakeups.
- TypeScript harness owns model-facing tool schemas and transforms.
- Adapter directories own protocol-specific code.

For PR cleanup, the main question to ask in each file is whether it belongs to one of those boundaries. Protocol details should not leak into the Rust runtime beyond generic worker configuration.
Loading