# cel-brief

[![crates.io](https://img.shields.io/crates/v/cel-brief.svg)](https://crates.io/crates/cel-brief)
[![docs.rs](https://docs.rs/cel-brief/badge.svg)](https://docs.rs/cel-brief)
[![CI](https://github.com/dimpagk92/cel-brief/actions/workflows/ci.yml/badge.svg)](https://github.com/dimpagk92/cel-brief/actions/workflows/ci.yml)

Composable prompt briefing for AI agents. Gather memory, perception, history,
tools, and user messages from pluggable sources; enforce token budgets; apply
governance; and emit receipts.

**Status:** Phases 1–4 shipped — core types, the `Source` trait and all built-in sources, the `Governance` trait, and the `BriefBuilder` (tokenizer + priority/budget pruning + `BriefReceipt`). The crate is feature-complete for assembling per-turn briefs.

## Purpose

Use `cel-brief` when an agent has many possible prompt inputs and needs one
structured, budgeted, governed package for a model call. Sources contribute
facts, messages, tools, history, or memory; the builder prunes to budget, runs
governance, and emits a receipt of what the model saw.

## Why

Every non-trivial AI agent — chat, code, computer-use, robotics — has to decide
what to put in the prompt: memory retrievals, current screen state, recent
actions, tool schemas, and the user's message. Today many projects solve that
inside the agent loop with string concatenation and homegrown budget code.
`cel-brief` makes that step explicit, structured, and inspectable.

## Three commitments

1. **Everything is a `Source`.** Memory, perception, history, tools — all the same trait. Plug in [`cel-memory`](https://crates.io/crates/cel-memory), Mem0, Letta, or your own — `cel-brief` doesn't care.
2. **Structured output, not a string.** `Brief { messages, tools, system, receipt }` is provider-agnostic; renderers map to OpenAI / Anthropic / local-model wire formats.
3. **Governance and budget are first-class.** Importance scoring, redaction hooks, token budgets, receipts — built in, not bolted on.

## Comparison

| | **cel-brief** | LangChain prompts | LlamaIndex `ServiceContext` | ad-hoc concat |
|---|---|---|---|---|
| Pluggable sources | ✓ trait-based | ✗ template strings | partial (retrievers only) | n/a |
| Token budgeting | ✓ priority-aware, per-source floor | ✗ caller's problem | ✗ caller's problem | ✗ rebuilt each turn |
| Importance-aware pruning | ✓ `[0.0, 1.0]` per contribution | ✗ | ✗ | ✗ |
| Governance / redaction hooks | ✓ `Governance` trait + receipts | ✗ | ✗ | ✗ |
| Receipts (what the model saw and why) | ✓ `BriefReceipt` with per-source stats | ✗ | ✗ | ✗ |
| Provider-agnostic output | ✓ structured `Brief` | partial | ✓ | depends |
| Async fan-out across sources | ✓ `async fn contribute` | ✗ sync | ✓ | n/a |
| Memory integration as a Source | ✓ `MemorySource` over any `MemoryProvider` | partial (`Memory` class) | ✓ | n/a |
| Perception / screen state as a Source | ✓ `PerceptionSource` over any backend | n/a | n/a | n/a |
| Language | Rust | Python | Python | n/a |

## Built-in sources

| Source | Feature | Priority | Notes |
|---|---|---|---|
| `SystemPromptSource` | default | Critical | Static system text. Never redactable. |
| `UserMessageSource` | default | Critical | Pulls `ctx.user_message`. Never redactable. |
| `ToolCatalogSource` | default | High | Owns `Vec<ToolSchema>`. |
| `HistorySource<H>` | default | Normal | Window of past N entries from any `HistoryStore`. Redactable. |
| `MemorySource<P>` | `memory` | Normal | Hybrid retrieval over any `cel_memory::MemoryProvider`. Redactable. |
| `PerceptionSource<P>` | `perception` | High | Defines the `PerceptionSnapshot` trait; downstream runtimes adapt their own perception engine into it. Redactable. |

## Quick start

```rust
use cel_brief::{
    BriefContext, BriefError, Source, SourceError, SystemPromptSource, TokenBudget,
    UserMessageSource,
};

let ctx = BriefContext::new(TokenBudget::default())
    .with_user_message("Hello!");

let sys = SystemPromptSource::new("You are a helpful assistant.");
let user = UserMessageSource::new();

let cs = sys.contribute(&ctx).await?;
assert_eq!(cs.len(), 1);
# Ok::<_, BriefError>(())
```

See [`examples/standalone.rs`](examples/standalone.rs) for a self-contained hand-fanout and [`examples/with_memory.rs`](examples/with_memory.rs) for the `cel-memory` integration:

```sh
cargo run --example standalone
cargo run --features memory --example with_memory
cargo run --example governance
```

The `governance` example shows a custom redaction hook and the resulting
`BriefReceipt` redaction records.

## `BriefBuilder`

The `BriefBuilder` fans out to every registered source, tokenizes and prunes to budget, runs governance, and returns a `Brief` plus its `BriefReceipt`:

```rust
let brief = BriefBuilder::new()
    .source(SystemPromptSource::new("You help with code."))
    .source(UserMessageSource::new())
    .source(MemorySource::new(memory.clone(), "embedded", 8))
    .source(ToolCatalogSource::new(tools))
    .governance(NoOpGovernance)        // swap in your own Governance
    .budget(TokenBudget::new(8000, 1024))
    .build(ctx).await?;

let response = openai.chat(brief.to_openai_request()).await?;
println!(
    "brief receipt: {} tokens, {} dropped, {} redactions",
    brief.receipt.total_tokens,
    brief.receipt.dropped.len(),
    brief.receipt.redactions.len(),
);
```

## Governance

`Governance::review(&mut draft, &ctx)` runs after budget pruning, before the brief is returned. The verdict is one of:

- `Allow` — the brief is fine as-is.
- `Redacted(Vec<RedactionRecord>)` — the hook mutated redactable content; the records describe what changed and which rule did it.
- `Rejected(String)` — policy violation; the builder returns `BriefError::Rejected`.

The default `NoOpGovernance` always allows. Production callers can plug in a real implementation that consults their own rules engine.

## Features

- `memory` — enable `MemorySource<P>` (depends on [`cel-memory`](https://crates.io/crates/cel-memory)).
- `perception` — enable the `PerceptionSnapshot` trait + `PerceptionSource<P>`. Perception backends live downstream: a runtime adapts its own live perception engine into a `PerceptionSnapshot`. This feature adds no dependency on any perception crate.

## Benchmark

A microbenchmark for hand-assembled briefs lives at [`benches/build.rs`](benches/build.rs). Target: under 50 ms p95 for a realistic brief. Run with:

```sh
cargo bench --features memory
```

## License

Apache-2.0
