{"_id":"@ai2070/crew","name":"@ai2070/crew","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ai2070/crew","version":"0.1.0","description":"Deterministic, replayable, bus-agnostic crew orchestration","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./memex":{"import":"./dist/memex/ai2070.js","types":"./dist/memex/ai2070.d.ts"}},"scripts":{"build":"tsc","test":"vitest run","test:watch":"vitest","prettier":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","prettier:check":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\""},"license":"Apache-2.0","peerDependencies":{"@ai2070/memex":"^0.13.0","zod":"^4.0.0"},"peerDependenciesMeta":{"@ai2070/memex":{"optional":true}},"devDependencies":{"@ai2070/memex":"^0.13.0","prettier":"^3.8.2","typescript":"^6.0.0","vitest":"^4.0.0","zod":"^4.0.0"},"gitHead":"982456070b0a32583df3822002d81f92dc6281ae","_id":"@ai2070/crew@0.1.0","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-B5WMbgBj0SBcVjvyDgAHAq0KAWGZUpw4aJL7T8r3vVCLbeK6TV3Y9zYYgFa0m+ELzRkeoRFsHfZ8ptchaU9JJQ==","shasum":"45f2488d18da48681da37c2394705d6ff2a62d5d","tarball":"https://registry.npmjs.org/@ai2070/crew/-/crew-0.1.0.tgz","fileCount":45,"unpackedSize":266160,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID4ndgdeSq+7WPlul4Qs6vcPzUqKrjFQAOx+16vu+DCjAiAd+qYD/vgpFyccZh4JoFrfi2oGaRRXcS70q+lk3EFL3g=="}]},"_npmUser":{"name":"lzl0","email":"makerseven7@gmail.com"},"directories":{},"maintainers":[{"name":"lzl0","email":"makerseven7@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/crew_0.1.0_1782085367367_0.5751358203309822"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-21T23:42:47.257Z","0.1.0":"2026-06-21T23:42:47.515Z","modified":"2026-06-21T23:42:47.671Z"},"maintainers":[{"name":"lzl0","email":"makerseven7@gmail.com"}],"description":"Deterministic, replayable, bus-agnostic crew orchestration","license":"Apache-2.0","readme":"# @ai2070/crew\n\nDeterministic, replayable, bus-agnostic crew orchestration for AI agents.\n\nA crew is a graph of typed roles and agents that takes an input through a sequence of phases and produces an output. This library does the orchestration — phase ordering, parallelism, voting, fault recovery, nested crews, scoped memory — without making any decisions about how agents actually run. Plug it onto whatever transport you want.\n\n## What it is\n\nA **state machine**, not an executor. The library:\n\n- Validates a crew shape and agent counts (Zod v4) and materializes a virtual graph.\n- Emits lifecycle events (`agent.step.requested`, `vote.resolved`, `fixer.invoked`, `nested.crew.completed`, …) that callers publish onto a bus.\n- Consumes responses (`agent.step.completed`, `agent.step.failed`, `agent.stream.chunk`) and advances accordingly.\n- Tracks parallel agents, dedups duplicate deliveries, manages timeouts, routes faults to fixer agents, and runs nested crews with optional memory isolation.\n\nIt is **not**:\n\n- A model runner. The library never calls L0 / OpenAI / Anthropic / anything.\n- A bus. There's no preferred transport (Net, EventEmitter, Kafka, in-process queue, anything works).\n- A persistence layer. Snapshots are values; storage is the caller's job.\n- An async framework. Hooks are sync; the loop is sync; everything is `(events in) → (events out)`.\n\n## Architecture\n\n```\n                ┌────────────────────────┐\n                │     crew library       │\n                │  (state machine only)  │\n                └────────────────────────┘\n                      ▲              │\n             inbound  │              │ outbound\n             events   │              │ lifecycle events\n                      │              ▼\n                ┌────────────────────────┐\n                │         bus            │  ← Net, EventEmitter, …\n                │  (caller's choice)     │\n                └────────────────────────┘\n                      ▲              │\n                      │              ▼\n                ┌────────────────────────┐    ┌──────────────┐\n                │     agent worker       │ →  │      L0      │\n                │   (caller's code)      │    │   (model)    │\n                └────────────────────────┘    └──────────────┘\n```\n\nThe library is the box at the top. Everything else is the caller's wiring.\n\n## Install\n\n```bash\nnpm install @ai2070/crew zod\n```\n\nOptional peers:\n\n```bash\nnpm install @ai2070/memex     # for scoped per-agent memory\n```\n\n## Quick Start\n\n```ts\nimport {\n  CrewShapeSchema,\n  CrewAgentsSchema,\n  buildCrewGraph,\n  createCrewSession,\n  systemClock,\n} from \"@ai2070/crew\";\n\n// 1. A crew is two JSON blobs: the shape (roles, permissions, capabilities)\n//    and the counts (how many of each role).\nconst shape = CrewShapeSchema.parse({\n  schema_version: \"1.0\",\n  name: \"RESEARCH_CREW\",\n  roles: [\n    {\n      role: \"caller\",\n      capabilities: { thinking_allowed: false },\n      permissions: { talk_to: [\"fixer\"], delegate_to: [], escalate_to: [], invite: [] },\n      first_input: true,\n      final_output: true,\n    },\n    {\n      role: \"merc\",\n      capabilities: { thinking_allowed: true, model: \"claude-sonnet-4-6\" },\n      system_prompt: \"You are a research operative. Gather facts efficiently.\",\n      permissions: { talk_to: [\"caller\"], delegate_to: [], escalate_to: [\"fixer\"], invite: [] },\n      amount: 4,\n    },\n    {\n      role: \"fixer\",\n      max_allowed: 1,\n      capabilities: { thinking_allowed: true },\n      activation: { on_fault: true, on_stall: true },\n      permissions: {\n        talk_to: [\"merc\", \"caller\"],\n        delegate_to: [\"merc\"],\n        escalate_to: [\"caller\"],\n        invite: [],\n      },\n    },\n  ],\n});\n\nconst counts = CrewAgentsSchema.parse({\n  schema_version: \"1.0\",\n  name: \"RESEARCH_CREW\",\n  agents: [\n    { role: \"caller\", amount: 1 },\n    { role: \"merc\", amount: 4 },\n    { role: \"fixer\", amount: 1 },\n  ],\n});\n\n// 2. Build the graph (validated, lint-checked, deterministic).\nconst graph = buildCrewGraph(shape, counts);\n\n// 3. Create a session.\nconst session = createCrewSession({\n  crewId: \"research-1\",\n  graph,\n  clock: systemClock(),\n});\n\n// 4. Drive the session against your bus. The optional `task` argument is\n//    session-level metadata — emitted once on `crew.started`, captured in\n//    snapshots, NOT propagated to per-step requests.\nconst initial = session.start(\"What sleep patterns do birds have?\", {\n  description: \"Research bird sleep patterns\",\n});\nfor (const e of initial) bus.publish(e);\n\nbus.subscribe(\"agent.step.completed\", (e) => {\n  for (const out of session.deliver(e)) bus.publish(out);\n});\n\n// Periodically tick to fire timeouts.\nsetInterval(() => {\n  for (const out of session.tick(Date.now())) bus.publish(out);\n}, 1000);\n```\n\nThe bus consumer (somewhere else in your stack) listens for `agent.step.requested`, runs whatever model/prompt logic it wants (e.g. calls L0), and publishes `agent.step.completed` back. The crew session never knows.\n\n## Mental Model\n\n- **The shape declares the topology** — which roles exist, how they relate, who can talk/delegate/escalate to whom, what activates fault recovery.\n- **The session emits requests** — when a phase starts, the session emits `agent.step.requested` for every agent in that role with a self-contained role snapshot, the input, and the optional memex context. Workers handle them however.\n- **The session waits for responses** — every request has a deterministic `correlationId`. Responses match by id. Phases wait for all agents to resolve before voting.\n- **Voting picks one output per phase** — `first_valid` (default), `majority`, `unanimous`, or `weighted_consensus`. The resolved value becomes the input to the next phase.\n- **The cascade is one-way** — phase N+1 only sees phase N's resolved output as its `input`. The original `rootInput` only reaches the caller (entry-point role). Each role brings its own memex view and own `system_prompt`. Workers compose the model prompt from those three pieces.\n- **Faults route to fixer** — if any role has `activation.on_fault`, an agent's failure spawns a fixer step whose response substitutes for the failed agent's slot in voting.\n- **Nested crews are recursive** — a role can declare `nested_crew`, which causes its agents to spawn inner sessions instead of going to the bus. Memory can be soft-isolated (shared adapter) or hard-isolated (forked, merged on completion).\n- **Task is session-level metadata** — `start(rootInput, task?)` accepts an optional `Task = { description: string }`. It rides on `crew.started` once and lives in the snapshot for replay/observability. It does **not** propagate onto per-step requests; it's not a substitute for the input cascade.\n- **Snapshot/resume preserves it all** — `session.snapshot()` returns a serializable record of every pending step's input, role snapshot, memex context, the session-level task, and inner-session state. `resumeCrewSession(snap, laterEvents, opts)` rebuilds it.\n\n### `system_prompt` is optional and unenforced\n\nEach role can declare a `system_prompt: string` on the shape. It's carried in the `RoleSnapshot` embedded in `agent.step.requested` so workers can use it as the model's system message. The library does **not** validate that LLM-driven roles (`capabilities.thinking_allowed: true`) actually have one — that's a worker-side / caller-side concern. If you want strict checks, walk the parsed shape yourself before `buildCrewGraph` and reject roles that need a prompt and don't have one.\n\n## Determinism\n\nEverything is deterministic given the inputs:\n\n- Same `(shape, counts, ordered inbound events)` → byte-identical outbound event log (after `canonicalize()`).\n- Inbound delivery order doesn't affect outbound — votes are tallied in graph declaration order.\n- `Clock` and ids are injected; the library never calls `Date.now()` or generates random data.\n- `tick()` is caller-driven — timeouts only fire when the caller calls it.\n\nThis makes replay testing trivial and snapshot/resume correct by construction.\n\n## What's in the box\n\n| Concept | Public surface | Source |\n|---|---|---|\n| Schema validation | `CrewShapeSchema`, `CrewAgentsSchema` | `src/schema/` |\n| Graph builder + linter | `buildCrewGraph`, `lintCrewShape` | `src/graph/` |\n| Event vocabulary | `CrewEvent` discriminated union | `src/events/types.ts` |\n| Canonical JSON | `canonicalize` | `src/events/canonical.ts` |\n| Correlation ids | `correlationId`, `hashHex` | `src/runtime/ids.ts` |\n| Clock injection | `systemClock`, `frozenClock` | `src/runtime/clock.ts` |\n| Hooks | `createHookRegistry`, `HookContext` | `src/runtime/hooks.ts` |\n| Session machine | `createCrewSession`, `resumeCrewSession` | `src/session/` |\n| Voting | `resolveVotes` | `src/voting/resolve.ts` |\n| Checkpoints | `createInMemoryCheckpointStore` | `src/checkpoint/` |\n| MemEX adapter (peer) | `createMemexAdapter` | `src/memex/ai2070.ts` (subpath `@ai2070/crew/memex`) |\n\nSee **[API.md](./API.md)** for the full reference.\n\n## Status\n\nEvery section of [PLAN.md](./PLAN.md) is implemented:\n\n- ✅ Schemas + graph builder + linter\n- ✅ Event vocabulary + canonical JSON + deterministic correlation ids\n- ✅ State machine: `start` / `deliver` / `tick` / `cancel`\n- ✅ Hooks (sync), permissions (ACL gates), fixer activation, nested crews\n- ✅ MemEX adapter (memex_context out, memex_commands in, hard isolation)\n- ✅ Snapshot / resume (with `ResumePolicy`) + `CheckpointStore`\n- ✅ Voting: `first_valid`, `majority`, `unanimous`, `weighted_consensus` (equal weights)\n\nDeferred to v2 (per plan): dynamic crews / `permissions.invite`, custom voting weight functions, `best_of_n` voting (needs scoring source), cost tracking.\n\n## License\n\nApache-2.0\n","readmeFilename":"README.md","_rev":"1-a27519d52f06cb5df58d27b17ab71502"}