pidgin_lang/lib.rs
1//! # Pidgin — A Compact Agent Handoff Protocol & Runtime
2//!
3//! Pidgin is a minimal, local-first protocol runtime for structured, validated
4//! handoffs between AI agents. It parses a compact nine-line text packet, runs
5//! it through a deterministic pipeline (validate → safety check → resolve →
6//! expand), and produces an executable YAML specification — all without calling
7//! a single LLM or opening a single network socket.
8//!
9//! ```text
10//! .pgn file (9 lines of key=value text)
11//! │
12//! ▼
13//! ┌─────────────┐
14//! │ Lexer │ winnow tokenizer, input limits
15//! └─────────────┘
16//! │
17//! ▼
18//! ┌─────────────┐
19//! │ Parser │ typed AST (PgnPacket)
20//! └─────────────┘
21//! │
22//! ▼
23//! ┌──────────────────┐
24//! │ Syntax Validator │ required fields present? types correct?
25//! └──────────────────┘
26//! │
27//! ▼
28//! ┌──────────────────┐
29//! │ Schema Validator │ workflow/mode/risk legal against registries?
30//! └──────────────────┘
31//! │
32//! ▼
33//! ┌──────────────────┐
34//! │ Safety Gate │ 9 rules (SG-1 through SG-9), fail closed
35//! └──────────────────┘
36//! │
37//! ▼
38//! ┌──────────────────┐
39//! │ Reference Resolver │ short refs → canonical paths/IDs
40//! └──────────────────┘
41//! │
42//! ▼
43//! ┌──────────────────┐
44//! │ Packet Expander │ fully-specified executable YAML
45//! └──────────────────┘
46//! │
47//! ▼
48//! ┌──────────────────┐
49//! │ Context Planner │ what to retrieve and how
50//! └──────────────────┘
51//! │
52//! ▼
53//! ┌──────────────────┐
54//! │ Token Estimator │ packet + context token cost
55//! └──────────────────┘
56//! │
57//! ▼
58//! ┌──────────────────┐
59//! │ Router Planner │ recommended executor
60//! └──────────────────┘
61//! │
62//! ▼
63//! ┌──────────────────┐
64//! │ Logger │ every step writes structured log
65//! └──────────────────┘
66//! │
67//! ▼
68//! Expanded packet, ready for: dry-run report | execution | human approval queue
69//! ```
70//!
71//! # Why This Exists
72//!
73//! Every serious study of multi-agent token cost in 2025–2026 converges on the
74//! same finding: the communication layer is the dominant cost and failure
75//! surface, not the reasoning layer. Agents pass verbose natural-language
76//! messages to each other — paragraphs of implicit instructions, unclearly
77//! scoped tasks, unvalidated assumptions. Each handoff costs hundreds or
78//! thousands of tokens. Worse, there's no validation: Agent B can interpret
79//! Agent A's message differently from what Agent A intended, and nobody audits
80//! it until something breaks.
81//!
82//! Pidgin formalizes the narrow waist between agents into a typed, validated,
83//! safety-checked wire format. The packet is nine lines. The entire runtime
84//! pipeline — lex, parse, validate, safety, resolve, expand, log — runs in
85//! single-digit milliseconds on a laptop. You can audit every handoff because
86//! every handoff is logged to a structured file.
87//!
88//! Pidgin is not an orchestrator. It does not run agents, it does not make
89//! decisions, it does not call models. It sits between whatever produces a task
90//! (a LangGraph node, a CrewAI agent, a human typing at a terminal) and
91//! whatever executes it (Claude, Codex, a shell script), and ensures the
92//! handoff is parseable, safe, and logged.
93//!
94//! # Quick Start
95//!
96//! ```bash
97//! cargo install pidgin-lang
98//! pgn init # scaffold .pidgin/ config
99//! pgn parse examples/basic/generic_task.pgn
100//! pgn run examples/basic/generic_task.pgn
101//! pgn check examples/basic/unsafe_contradiction.pgn
102//! pgn measure examples/basic/generic_task.pgn
103//! pgn doctor
104//! ```
105//!
106//! # Packet Grammar
107//!
108//! A Pidgin packet is plain text with a strict, unambiguous grammar. There is
109//! no nesting, no optional syntax, no inline markup — every byte is either a
110//! header, a field, a list element, or a comment.
111//!
112//! ## Header
113//!
114//! ```text
115//! @<directive> <run_id>
116//! ```
117//!
118//! Four directives:
119//!
120//! | Directive | Purpose |
121//! |-----------|---------|
122//! | `@run` | A task for an agent to execute |
123//! | `@result` | The outcome of a completed task |
124//! | `@approval` | Human sign-off for a critical-risk task |
125//! | `@context` | A request for additional information |
126//!
127//! `run_id` is a dotted identifier — `task.example`, `docs.review.2026-06-01` —
128//! that uniquely identifies the handoff across the system.
129//!
130//! ## Fields
131//!
132//! Every field is `key=value` on its own line. Whitespace around `=` is not
133//! allowed, which eliminates an entire class of lexer ambiguities.
134//!
135//! ```text
136//! wf=generic_review # bare scalar
137//! risk=med # bare scalar (allowed values depend on workflow)
138//! in=[primary_subject,refs] # list — comma-separated, no spaces
139//! note="Draft only" # quoted string (for free text)
140//! ```
141//!
142//! ### Scalar fields
143//!
144//! | Field | Type | Appears On |
145//! |-------|------|------------|
146//! | `wf` | bare word | all directives |
147//! | `mode` | bare word | `@run`, `@context` |
148//! | `risk` | `low │ med │ high │ crit` | `@run`, `@approval` |
149//! | `human` | `yes │ no` | `@run`, `@approval` |
150//! | `status` | `ok │ fail │ partial` | `@result`, `@approval` |
151//! | `ttl` | integer | all directives |
152//! | `note` | quoted string | all directives |
153//!
154//! ### List fields
155//!
156//! | Field | Appears On | Contents |
157//! |-------|------------|----------|
158//! | `in` | `@run`, `@context` | Input references |
159//! | `out` | `@run`, `@result` | Output references |
160//! | `do` | `@run` | Actions to perform |
161//! | `deny` | `@run` | Actions to explicitly deny |
162//! | `produced` | `@result` | References to produced artifacts |
163//!
164//! ## Reference syntax
165//!
166//! Inside list fields, references can be either:
167//!
168//! - **Namespaced:** `namespace:id` — e.g. `file:src/main.rs`, `ep:UNIT012`
169//! - **Bare alias:** `primary_subject` — resolved through `REFERENCE_ALIASES.yaml`
170//!
171//! Built-in namespaces:
172//!
173//! | Namespace | What It References |
174//! |-----------|-------------------|
175//! | `ep` | Entity pointer — a content item, document, or record |
176//! | `rb` | Rollback target — a point-in-time snapshot |
177//! | `ledger` | Ledger entry — an audit record |
178//! | `claim` | Claim — a config key, channel, or property |
179//! | `policy` | Policy — a rule file or constraints document |
180//! | `skill` | Skill — a capability or tool definition |
181//! | `wf` | Workflow — a workflow definition |
182//! | `file` | File path (checked against safety rules) |
183//! | `folder` | Directory path (checked against safety rules) |
184//! | `dash` | Dashboard — a view or report |
185//! | `queue` | Queue — a named message queue |
186//!
187//! # Safety Gate (SG-1 through SG-9)
188//!
189//! The safety gate is the core safety mechanism. It is a separate pipeline stage
190//! — it runs after parsing and validation but before reference resolution and
191//! expansion. It cannot be disabled, skipped, or overridden by packet fields.
192//!
193//! Every rule fails closed: if the gate cannot determine whether a rule applies,
194//! it treats the rule as violated.
195//!
196//! ## SG-1 — Contradiction
197//!
198//! An action cannot appear in both `do` and `deny`. If a packet says both
199//! "do publish" and "deny publish", something is wrong. The runtime refuses
200//! to guess which intent is correct.
201//!
202//! ## SG-2 — Missing Human Approval
203//!
204//! If a packet requests a human-gated action (publishing, deleting, sending
205//! credentials, etc.) without `human=yes`, the gate blocks. Human-gated actions
206//! are defined in `ACTION_REGISTRY.yaml` as the `human_gated` tier plus
207//! `human_required_actions` in `SAFETY_RULES.yaml`.
208//!
209//! ## SG-3 — Forced Human Approval
210//!
211//! High- or critical-risk packets cannot opt out of human review. Even if the
212//! packet declares `human=no`, the gate overrides it. This prevents a risky
213//! packet from bypassing human oversight by lying about its own `human` field.
214//!
215//! ## SG-4 — Private Path Access
216//!
217//! Any `file:` or `folder:` reference that resolves to a path matching a
218//! private path pattern (`.env`, `.ssh/`, `*.pem`, `secrets/`, etc.) is
219//! blocked. Paths are canonicalized before matching, so symlink tricks and
220//! traversal sequences (`../../etc/passwd`, `%2e%2e`) are caught.
221//!
222//! ## SG-5 — Unknown Workflow
223//!
224//! The `wf` field must match a workflow defined in `WORKFLOW_REGISTRY.yaml`.
225//! An unknown workflow is not a no-op — it is a bug or an attack.
226//!
227//! ## SG-6 — Invalid Mode
228//!
229//! The `mode` field must be in the workflow's `allowed_modes` list. A
230//! `generic_review` workflow should never be executed in `production` mode.
231//!
232//! ## SG-7 — Note Isolation
233//!
234//! The `note` field stores free text. No pipeline stage reads or interprets it.
235//! This is structural: the note is an opaque string throughout the entire
236//! pipeline. If you want an agent to read a note, the expanded packet makes
237//! it available, but the runtime never acts on its contents. This closes
238//! the most obvious prompt-injection surface.
239//!
240//! ## SG-8 — Unresolved Required Input
241//!
242//! If a required input reference fails to resolve (the alias is not in
243//! `REFERENCE_ALIASES.yaml` and the namespace:id doesn't exist), expansion
244//! is blocked. Running with missing inputs produces silent failures.
245//!
246//! ## SG-9 — Critical Risk Requires Approval Packet
247//!
248//! A packet with `risk=crit` requires a separate `@approval` packet with
249//! `status=ok` before it can be expanded. A single `human=yes` on the same
250//! packet is not enough — the two-packet pattern ensures separation of
251//! concerns.
252//!
253//! # Multi-Agent Integration
254//!
255//! Pidgin is the handoff *format*, not the orchestrator. Here is how it fits
256//! into various agent architectures:
257//!
258//! ```text
259//! expanded .pgn
260//! Orchestrator ─── .pgn ──→ Pidgin ──────────────────────────→ Executor Agent
261//! (LangGraph, ←────── (validate, safety, ←────────── (Claude, Codex,
262//! CrewAI, result resolve, expand, result shell, tool)
263//! A2A, MCP) log)
264//! ```
265//!
266//! ## LangGraph
267//!
268//! Add a Pidgin node between any two agent nodes. The Pidgin node parses and
269//! validates the `.pgn` packet produced by the source agent, routes to the
270//! destination agent based on the expanded packet, and logs the handoff to the
271//! shared graph state. The flow remains typed and auditable.
272//!
273//! ## CrewAI
274//!
275//! Each CrewAI agent produces a `.pgn` packet as its task output. A custom
276//! CrewAI tool wraps `pgn run` (or the library) to validate inter-agent
277//! handoffs. Invalid or unsafe handoffs block before reaching the next agent.
278//!
279//! ## A2A (Agent2Agent)
280//!
281//! Pidgin's expanded Run Packet maps naturally into an A2A Task. The `.pgn`
282//! format becomes the wire representation inside trust boundaries; the expanded
283//! A2A Task is the representation crossing them. Pidgin's safety gate provides
284//! the guardrails that A2A intentionally leaves to implementors.
285//!
286//! ## MCP (Model Context Protocol)
287//!
288//! Pidgin can run as an MCP server, exposing `parse`, `validate`, `check`,
289//! and `expand` as MCP tools. Agents connected through MCP call these tools
290//! as part of their workflow, getting structured, validated handoffs without
291//! leaving the MCP protocol.
292//!
293//! ## Python SDK
294//!
295//! The Python SDK (scaffolded in `python/`) wraps the `pgn` binary via
296//! subprocess, giving Python-based orchestrators (LangChain, CrewAI,
297//! custom scripts) typed Pydantic models for packets, safety results, and
298//! expanded output. PyO3 bindings are on the roadmap.
299//!
300//! # Host Configuration
301//!
302//! Every Pidgin host provides a `.pidgin/` directory with five YAML config
303//! files. Run `pgn init` to scaffold default versions.
304//!
305//! | File | What It Defines |
306//! |------|----------------|
307//! | `PIDGIN_RUNTIME_CONFIG.yaml` | Host name, log directory, default deny list, input limits (1 MB max packet, 100 max fields, 10K max field length, 10 MB max config) |
308//! | `WORKFLOW_REGISTRY.yaml` | Workflow definitions — each with risk default, allowed modes, required inputs, expected outputs, recommended executor, and human-approval requirement |
309//! | `ACTION_REGISTRY.yaml` | Three tiers: `safe` (always allowed), `controlled` (allowed with validation), `human_gated` (requires `human=yes`) |
310//! | `SAFETY_RULES.yaml` | Default deny list, gitignore-style private path patterns, human-required actions and risk levels |
311//! | `REFERENCE_ALIASES.yaml` | Short-name aliases mapping bare identifiers to full `namespace:id` references |
312//!
313//! Config files are loaded at startup from the host root (`.` or
314//! `$PIDGIN_ROOT_DIR`). Each file is validated for structure and key presence.
315//!
316//! # CLI
317//!
318//! The `pgn` binary exposes every pipeline stage as a subcommand plus
319//! infrastructure commands:
320//!
321//! | Command | Action |
322//! |---------|--------|
323//! | `init` | Scaffold `.pidgin/` directory |
324//! | `parse <path>` | Lex and parse, print AST |
325//! | `validate <path>` | Syntax + schema validation |
326//! | `check <path>` | Full guard: validate → safety → resolve |
327//! | `resolve <path>` | Expand short references |
328//! | `expand <path>` | Full pipeline → executable YAML |
329//! | `run <path>` | Full pipeline + structured logging |
330//! | `measure <path>` | Estimate token cost |
331//! | `compare <path>` | Compare vs verbose token cost |
332//! | `context-plan <path>` | Build retrieval plan |
333//! | `doctor` | Check host configuration health |
334//! | `docs` | Print full protocol documentation |
335//!
336//! Exit codes:
337//! - `0` success
338//! - `1` validation error (syntax or schema)
339//! - `2` safety gate blocked
340//! - `3` unresolved required reference
341//! - `4` configuration error
342//! - `5` internal error (file a bug)
343//!
344//! # Using as a Library
345//!
346//! Add to `Cargo.toml`:
347//!
348//! ```toml
349//! [dependencies]
350//! pidgin-lang = "0.1"
351//! ```
352//!
353//! Then compose your own pipeline:
354//!
355//! ```rust
356//! use pidgin_lang::parser::parse_packet;
357//! use pidgin_lang::safety::check_safety;
358//! use pidgin_lang::expander::expand_to_run_packet;
359//!
360//! let packet = parse_packet("@run my.task\nwf=generic_review\nmode=draft")
361//! .expect("valid packet");
362//! ```
363//!
364//! Every stage function is public and well-typed. You can:
365//!
366//! - Call only the parser (if you just need an AST)
367//! - Call validate + safety without resolving (quick check)
368//! - Skip the safety gate in test/development only
369//! - Add custom stages before or after any built-in stage
370//! - Implement your own logger by implementing the `Logger` trait
371//!
372//! # Safety Properties
373//!
374//! Beyond the safety gate, several cross-cutting properties hold:
375//!
376//! - **No network calls in core.** The runtime never opens a socket or makes
377//! an HTTP request. There is no network attack surface.
378//! - **No LLM in the hot path.** Every stage is deterministic. No model is
379//! called at any point. The safety gate is algorithmic, not probabilistic.
380//! - **Fail closed.** Every uncertain decision blocks. Unknown workflow →
381//! blocked. Missing required input → blocked. Ambiguous reference → blocked.
382//! - **Path containment.** All file references are canonicalized and checked
383//! against the host root. No traversal, no symlink escape, no encoded bypass.
384//! - **Input limits.** Packets are bounded (1 MB, 100 fields, 10K per field).
385//! Config files are bounded (10 MB). The lexer rejects oversized input before
386//! any heavy processing.
387//! - **Sanitized logging.** User values in logs are filtered to printable ASCII
388//! with length caps. No log injection, no CSV injection.
389//! - **Append-only logs.** Every log write completes (flush + close) before
390//! the next pipeline stage starts. No log loss on crash.
391//!
392//! # Modules
393//!
394//! The library is organized into modules corresponding to pipeline stages and
395//! supporting infrastructure:
396
397/// Typed AST types — `PgnPacket`, `FieldValue`, `PacketDirective`, and related
398/// data structures representing a parsed Pidgin packet in memory.
399pub mod ast;
400
401/// Context planner — decides what information to retrieve based on a packet's
402/// workflow and resolved references, producing a structured retrieval plan.
403pub mod context;
404
405/// Error types — `ParseError`, `ValidationError`, `SafetyError`, and other
406/// error enums with typed variants, source chaining, and formatted messages.
407pub mod errors;
408
409/// Packet expander — takes a validated, safety-checked, resolved packet and
410/// produces a fully-specified executable YAML packet ready for consumption by
411/// executors (RunPacket, ResultPacket, ApprovalPacket, ContextPacket).
412pub mod expander;
413
414/// Lexer/tokenizer — winnow-based tokenizer that converts raw `.pgn` text
415/// into structured tokens: header, fields, scalars, lists, comments. Enforces
416/// input size limits (1 MB max, 100 fields max, 10,000 chars per field).
417pub mod lexer;
418
419/// Structured logging — append-only CSV logging for every pipeline stage,
420/// with user-value sanitization (printable ASCII only, length-capped,
421/// newline-escaped) to prevent log injection and CSV injection.
422pub mod logging;
423
424/// Token estimation and cost metrics — estimates token cost of raw text and
425/// structured packets using configurable models. Also supports comparing the
426/// same handoff expressed as Pidgin vs verbose natural language.
427pub mod metrics;
428
429/// Packet parser — winnow-based grammar parser that converts lexed tokens
430/// into a typed `PgnPacket` AST with full field validation, directive-specific
431/// required-field checks, and human-readable parse errors.
432pub mod parser;
433
434/// Registry loader — deserializes YAML configuration files from `.pidgin/`
435/// (WorkflowRegistry, ActionRegistry, SafetyRules, ReferenceAliases,
436/// PidginRuntimeConfig) with validation of structure and required keys.
437pub mod registry;
438
439/// Reference resolver — maps short references (`namespace:id` and bare aliases)
440/// to real filesystem paths or identifiers, with canonicalization, host-root
441/// containment checks, and symlink-traversal protection.
442pub mod resolver;
443
444/// Route planner — recommends an executor for a packet based on workflow
445/// configuration and safety results, with fallback chains and logging.
446pub mod router;
447
448/// Safety gate — enforces 9 safety rules (SG-1 through SG-9) including
449/// contradiction detection, human-approval requirements, private-path
450/// protection, workflow validation, mode validation, note isolation,
451/// required-ref checking, and critical-risk dual-packet approval.
452pub mod safety;
453
454/// Validator — two-stage validation: syntax validation checks structural
455/// completeness (required fields present, types correct) and schema
456/// validation checks semantic legality (workflow in registry, mode allowed,
457/// risk level valid).
458pub mod validator;
459
460#[cfg(test)]
461mod tests;