Skip to content

Commit 1bad7b8

Browse files
Merge pull request #41 from andreibesleaga/feat/v2.0
Feat/v2.0
2 parents 4a75774 + fe979cd commit 1bad7b8

201 files changed

Lines changed: 18513 additions & 3370 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ MESSAGING_DRIVER=bullmq
3333
# Leave unset to use ConsoleSpanExporter (dev mode — verbose, not suitable for production)
3434
# OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318/v1/traces
3535

36-
# Comma-separated list of agent IDs this worker node serves (required)
36+
# Process role for the single combined image (src/main/index.ts dispatches on it).
37+
# ROLE=gateway → HTTP / WebSocket / A2A / MCP front door only (no task-consuming actors)
38+
# ROLE=worker → loads the AGENT_IDS pool and wires real LLM-backed handlers
39+
# Unset defaults to "gateway". (ADR-013 gateway/worker split.)
40+
# ROLE=gateway
41+
42+
# Comma-separated list of agent IDs this worker node serves (required for ROLE=worker)
3743
AGENT_IDS=researcher,writer,editor
3844

3945
# LLM Configuration — Standard OpenAI OR OpenRouter (or any OpenAI-compatible API)
@@ -86,6 +92,7 @@ OPENAI_API_KEY=your_openai_api_key_here
8692

8793
# ── Security: Redis password (recommended) ─────────────────────
8894
# Set this to require a password on the Redis server (matches docker-compose.yml)
95+
# (compose-only) — consumed by docker-compose, not read directly by src/; fold it into REDIS_URL below.
8996
# REDIS_PASSWORD=<random 32+ chars>
9097
# When set, use: REDIS_URL=redis://:${REDIS_PASSWORD}@localhost:6379
9198

@@ -98,6 +105,36 @@ OPENAI_API_KEY=your_openai_api_key_here
98105
# JWT secret for agent/orchestrator service tokens. When set, POST /a2a/rpc requires Bearer token.
99106
# A2A_JWT_SECRET=<random 32+ bytes, base64-encoded>
100107

108+
# ── Federation: MCP server (Phase M — OFF by default) ──────────────────────────────────────
109+
# Exposes allow-listed Tools/Resources/Prompts + elicitation (HITL consent) over Streamable HTTP,
110+
# behind the gateway's security chain (rate-limit + the A2A_JWT_SECRET bearer above). See docs/federation/MCP.md.
111+
# ⚠ SECURITY: the MCP route is authenticated ONLY when A2A_JWT_SECRET (above) is set. If you enable
112+
# MCP in production WITHOUT A2A_JWT_SECRET, the MCP surface (dispatch_task + agent resources) is
113+
# UNAUTHENTICATED (still rate-limited + dispatch is elicitation-consent-gated). Set A2A_JWT_SECRET.
114+
# MCP_SERVER_ENABLED=false # master switch (default false)
115+
# MCP_SERVER_PATH=/mcp # mount path on the gateway
116+
# MCP_DISPATCH_CONSENT=true # require elicitation consent before dispatch_task (fail-closed)
117+
# Optional least-privilege allow-lists (CSV). Unset ⇒ the full curated set ships; empty ⇒ none of that kind.
118+
# MCP_ALLOWED_TOOLS=dispatch_task
119+
# MCP_ALLOWED_RESOURCES=agents,agent-status
120+
# MCP_ALLOWED_PROMPTS=delegate_task
121+
122+
# ── Economics / FinOps (Phase E — OFF by default) ──────────────────────────────────────────
123+
# Fleet-wide rate + cost control on top of per-task accounting (which is unchanged). 0 = unlimited.
124+
# See docs/economics/ECONOMICS.md. The per-task MAX_TOKEN_BUDGET above is separate and still applies.
125+
# ECONOMICS_ENABLED=false # master switch (default false)
126+
# ECONOMICS_MAX_REQUESTS_PER_WINDOW=0 # request-rate ceiling per scope (0 = unlimited)
127+
# ECONOMICS_MAX_COST_PER_WINDOW=0 # cost-unit budget per tenant/agent scope (0 = unlimited)
128+
# ECONOMICS_GLOBAL_COST_CEILING=0 # cost-unit ceiling across ALL scopes (0 = unlimited)
129+
# ECONOMICS_WINDOW_SECONDS=60 # sliding window length (seconds)
130+
# ECONOMICS_DEGRADE_THRESHOLD=0.75 # utilization at/above which to DEGRADE (run cheaper)
131+
132+
# ── Governance / Action Gate (Phase G — OFF by default) ────────────────────────────────────
133+
# Non-bypassable enforcement (firewall + breaker + cost + policy + kill-switch), hash-chained audit,
134+
# policy-as-code, agent registry/kill-switch, memory RBAC. See docs/governance/GOVERNANCE.md.
135+
# GOVERNANCE_ENABLED=false # master switch (default false → gate is a no-op allow)
136+
# GOVERNANCE_POLICIES_PATH=/etc/kaiban/policies.yml # optional policy-as-code file (see src/governance/policies.yml)
137+
101138
# ── Security: Redis Channel Signing (optional, strongly recommended in production) ─────────
102139
# HMAC-SHA256 secret for signing state events on Redis pub/sub. When set, fake state injection
103140
# from anyone with Redis access is blocked.
@@ -109,13 +146,16 @@ OPENAI_API_KEY=your_openai_api_key_here
109146

110147
# ── Telemetry: OTLP auth (optional) ────────────────────────────
111148
# When using an authenticated OTLP collector, add an Authorization header:
149+
# (reserved — not yet wired in code; honored by the OTLP SDK env contract, not parsed by src/)
112150
# OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <token>
113151

114152
# ── HITL Decision Options ───────────────────────────────────────
115153
# Comma-separated list of valid human decisions for the HITL gate.
116154
# The gateway validates incoming board decisions against this list.
117-
# VIEW is informational only (re-prompts terminal); it does not resolve the gate.
118-
VALID_HITL_DECISIONS=PUBLISH,REVISE,REJECT,VIEW
155+
# This matches the code default (src/main/config.ts). Do NOT add VIEW here:
156+
# VIEW is an interactive terminal-only action that re-prompts the orchestrator
157+
# prompt — it does not resolve the gate and is NOT a valid board/gateway decision.
158+
VALID_HITL_DECISIONS=PUBLISH,REVISE,REJECT
119159

120160
# ── Agent Identity (set per-node process / container) ───────────
121161
# Each agent node reads its own identity from AGENT_ID.
@@ -137,6 +177,11 @@ VALID_HITL_DECISIONS=PUBLISH,REVISE,REJECT,VIEW
137177
#
138178
AGENT_ID=researcher
139179

180+
# Public base URL advertised in the A2A AgentCard (/.well-known/agent-card.json).
181+
# Set this to the gateway's externally-reachable URL for federated deployments
182+
# behind a public hostname/proxy. Defaults to http://localhost:${PORT}.
183+
# A2A_PUBLIC_URL=https://agents.example.com
184+
140185
# ── Example Orchestrator Vars (set in docker-compose or shell) ──
141186
# These are only used by the example orchestrators, not the core library.
142187
#
@@ -147,6 +192,11 @@ AGENT_ID=researcher
147192
# WRITE_WAIT_MS=240000 # Timeout for write phase (ms)
148193
# EDIT_WAIT_MS=300000 # Timeout for edit phase (ms)
149194
#
195+
# WORKFLOW_ID — both example orchestrators read it to namespace the Redis
196+
# checkpoint (crash-safe resume). Unset → derived from the topic/context, so a
197+
# restart with the same input RESUMES; set a unique value to force a fresh run.
198+
# WORKFLOW_ID=my-run-001
199+
#
150200
# Global Research orchestrator:
151201
# GATEWAY_URL=http://localhost:3000 # Gateway HTTP endpoint
152202
# QUERY=The Future of AI Agents # Research query
@@ -168,3 +218,9 @@ AGENT_TIMEOUT_MS=300000
168218
# Max cumulative tokens per AgentStatePublisher instance before it raises an error.
169219
# Use to cap LLM spend per agent process. 0 = unlimited (default).
170220
MAX_TOKEN_BUDGET=0
221+
# Workflow-level spend guard for the example orchestrators (checked between phases
222+
# and before each revision). Caps the WHOLE workflow's cumulative spend so a runaway
223+
# (e.g. repeated revisions) stops gracefully instead of draining the budget. The
224+
# example compose files default MAX_WORKFLOW_COST_USD to 0.50; 0 = unlimited.
225+
MAX_WORKFLOW_COST_USD=0
226+
MAX_WORKFLOW_TOKENS=0

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ jobs:
8585
- name: Dependency audit (fail on HIGH+; moderates triaged in SECURITY.md)
8686
run: npm audit --audit-level=high
8787
- name: Generate CycloneDX 1.6 SBOM
88-
run: npx --yes @cyclonedx/cyclonedx-npm@latest --output-format JSON --spec-version 1.6 --output-file sbom.json
88+
# --ignore-npm-errors: `npm ls` exits non-zero on a harmless dev-only peer
89+
# mismatch (madge→precinct wants TypeScript 5.x while the project pins 6.x);
90+
# the installed tree is valid, so the SBOM is still complete and accurate.
91+
run: npx --yes @cyclonedx/cyclonedx-npm@latest --ignore-npm-errors --output-format JSON --spec-version 1.6 --output-file sbom.json
8992
- name: Upload SBOM
9093
uses: actions/upload-artifact@v7
9194
with:

.github/workflows/release.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ jobs:
2929
- run: npm run test:coverage
3030
- name: CycloneDX 1.6 SBOM
3131
run: npx --yes @cyclonedx/cyclonedx-npm@latest --output-format JSON --spec-version 1.6 --output-file sbom.json
32-
- name: Pack tarball
33-
run: npm pack
32+
- name: Pack tarball (from a staging dir → Apache-2.0 LICENSE, no GPL leak)
33+
# The published library is Apache-2.0 but the repo root LICENSE is GPL-3.0
34+
# (the app/aggregate). `npm pack` force-includes the root LICENSE; pack from
35+
# a clean staging dir whose only LICENSE is LICENSE-APACHE. See docs/RELEASE.md.
36+
run: bash scripts/pack-staging.sh
3437
- name: Subject hashes (for provenance)
3538
id: hash
3639
run: echo "hashes=$(sha256sum *.tgz sbom.json | base64 -w0)" >> "$GITHUB_OUTPUT"

.gitignore

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
*.swp
1111
.DS_Store
1212

13+
.agents
1314
.claude
15+
.cline
16+
.clinerules
1417
.gemini
1518
.github/skills
1619
.github/copilot-instructions.md
@@ -22,14 +25,23 @@ coverage
2225
/agents/
2326
certs
2427
runs
28+
project
29+
setup-context.sh
2530

2631
# In-repo audit deliverables — kept locally + mirrored in OpenSourceAudit, not published in the repo
2732
docs/audit/
2833

34+
AGENTS.md
35+
GEMINI.md
2936
CHANGES_LOG.md
3037
CHANGES_GITLOG.md
31-
38+
KAIBAN-v2.0-MASTER-PLAN.md
39+
BOOTSTRAP_MISSION.md
3240

3341
temp/
3442
reports/
3543
.stryker-tmp/
44+
45+
# Phase 4b staging-publish artifacts
46+
dist-staging/
47+
*.tgz

CHANGELOG.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,95 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
## [2.0.0] - in progress (`feat/v2.0`)
10+
11+
Major release — breaking changes are documented in `MIGRATION.md`. Authoritative plan:
12+
`KAIBAN-v2.0-MASTER-PLAN.md`.
13+
14+
### Added
15+
- **A2A v0.3 federation** via `@a2a-js/sdk`: the gateway answers `message/send`,
16+
`message/stream` (SSE), `tasks/get`, `tasks/cancel` on `POST /a2a/rpc`; AgentCard v0.3
17+
(object `capabilities` + abilities in `skills[]`). (BETA.1, ADR-015)
18+
- **MCP server** — first-party Model Context Protocol surface (Tools / Resources / Prompts /
19+
Elicitation) over Streamable HTTP. (BETA.2, ADR-017)
20+
- **Resilience** — the single-active orchestrator is promoted to `src/shared` (reusable, published):
21+
Redis checkpoint/resume, liveness/readiness probes, graceful drain, DLQ replay. (BETA.2, ADR-018)
22+
- **Economics / FinOps** — fleet-wide rate + cost control (token bucket + cost reservation),
23+
**default-off**. (BETA.3, ADR-019)
24+
- **Governance Action Gate****default-off, non-bypassable when enabled**: hash-chained audit,
25+
policy-as-code, kill-switch; hot-path enforcement wired into `AgentActor`. (BETA.3, ADR-020/021)
26+
- **Universal AMQP driver seam** — declared `amqplib` seam, unimplemented stub (coverage-excluded).
27+
(BETA.1, ADR-016)
28+
- **`dispatchToAgent`** actor-mailbox primitive in `src/shared` (replaces the removed `tasks.create`
29+
RPC). _(The examples also gain a local `parseAgentCardSkills` helper to read v0.3 AgentCards — example
30+
code, not a published library export.)_
31+
- **Workflow budget guard** (`MAX_WORKFLOW_COST_USD` / `MAX_WORKFLOW_TOKENS`) in both example
32+
orchestrators — checked between phases and before each revision; **graceful STOPPED on breach**
33+
(default `0.50` in the example compose files; `0` = unlimited; separate from per-agent
34+
`MAX_TOKEN_BUDGET`).
35+
- **Playwright visual baselines** for the React board + the two static example viewers
36+
(`cd board && npm run test:visual`).
37+
- **`scripts/smoke-consumer.sh`** — packs the Apache tarball and verifies a fresh consumer can import
38+
the public surface from both entry points (`.` and `./shared`).
39+
- **Packaging:** `./shared` subpath export + two-entry api-extractor; staging-dir `npm pack`
40+
(Apache-only artifact, no GPL leak). (BETA.1, ADR-011)
41+
- **COMPLIANCE** cross-walk and the **v2.1 roadmap**.
42+
43+
### Changed
44+
- **License (BREAKING):** the published npm library is now **Apache-2.0** (was GPL-3.0); the full
45+
application / board / examples remain **GPL-3.0** (dual-license — see `LICENSING.md`, ADR-011).
46+
- **Dependencies:** KaibanJS 0.24.2, TypeScript 6.0, OpenTelemetry 0.219/0.77, bullmq 5.79, dotenv 17
47+
— all latest stable, **0 vulnerabilities** (ADR-012).
48+
- **gateway / worker ROLE split** — a single image runs as `ROLE=gateway|worker`. (BETA.1, ADR-013)
49+
- **AbortSignal cancellation** — an in-flight LLM call is aborted on task timeout / `tasks/cancel`
50+
(the bridge owns the LLM instance). (BETA.1, ADR-014)
51+
- **Examples migrated to A2A v0.3** — removed all `tasks.create` / `tasks.get` / `agent.status`
52+
usage; both examples dispatch via `dispatchToAgent` and read AgentCard `skills[]`.
53+
- **Gateway HITL delivery** — the durable per-task BRPOP-list write now precedes the pub/sub
54+
publish, and the board is ACK'd only after **both** succeed (a missed pub/sub message stays
55+
recoverable via the list fallback).
56+
- **`CompletionRouter` subscribes lazily** (on the first `wait()`) — a router that never waits no
57+
longer consumes the shared completed queue (fixes a competing-consumer hang between the gateway's
58+
A2A-executor router and an orchestrator router).
59+
- **Kafka driver** now throws a clear error on a 2nd `subscribe()` (explicit one-topic-per-driver
60+
contract) instead of silently breaking.
61+
- **BullMQ driver** sets job-retention defaults (`removeOnComplete` / `removeOnFail`, bounding Redis
62+
growth) and registers a worker `error` listener.
63+
- **A2A executor** logs the underlying error server-side on failure (the wire response stays generic).
64+
65+
### Fixed
66+
- **HITL re-arm loop** — the terminal prompt no longer re-arms after a decision arrives (board OR
67+
terminal) or after stdin EOF; fixes the **REVISE** infinite re-prompt spin (100% CPU, process
68+
never exits, Ctrl-C ineffective) and restores the second HITL gate on the revised draft.
69+
- **Board hangs on RUNNING after a hard failure** — both orchestrators now publish a terminal
70+
STOPPED state on error, so the board reflects the failure instead of hanging.
71+
- **Board store** — malformed state deltas with no `agentId` / `taskId` are skipped (the Zustand map
72+
is never keyed by `"undefined"`).
73+
- **Fan-out/fan-in result↔index mismatch** (global-research, plan finding C1/HIGH) — search results
74+
are now mapped to their **dispatch** index by `taskId` (was indexed by `waitAll` completion-order
75+
position), so searchers that finish out of order get the correct sub-topic, node label and logged
76+
taskId.
77+
- **Governance Action Gate fails closed** on a throwing validator; MCP-without-auth warning.
78+
- **Byte-accurate data caps** — the 64 KB outbound-message and 20 KB state-event-result caps are now
79+
measured in **UTF-8 bytes** (`Buffer.byteLength`), truncating on a codepoint boundary (was UTF-16
80+
`.length`, which let multi-byte payloads exceed the byte cap).
81+
- **Structured agent output** — the KaibanJS bridge now JSON-stringifies a non-string (object) LLM
82+
result instead of emitting `"[object Object]"`.
83+
- **Model-pricing accuracy**`estimateCost` normalizes OpenRouter slugs / dated suffixes
84+
(`openai/gpt-4o-mini`, `gpt-4o-2024-08-06`, `anthropic/claude-3-5-sonnet`) before pricing lookup,
85+
and warns on a default-pricing fallback (was exact-match only → slugs silently mis-priced).
86+
- **Config robustness** — numeric env vars are parsed NaN-safe (`AGENT_TIMEOUT_MS=abc` no longer
87+
yields `setTimeout(…, NaN)` / instant timeouts).
88+
- **Actor robustness** — a timed-out task handler's late rejection no longer surfaces as an
89+
`unhandledRejection`.
90+
- **Global-research budget guard** now runs after **every** phase (search, write, governance,
91+
editorial), matching blog-team.
92+
- A2A input validation hardened; de-stubbed `IDLE` / `TODO` placeholders. (BETA.1)
93+
94+
### Security
95+
- The published library ships **0 HIGH/CRITICAL advisories**; release flow keeps SBOM + SLSA
96+
provenance + cosign signing (ADR-012).
97+
998
## [1.5.0-beta] - 2026-06-16
1099

11100
### Pre-merge hardening pass (audit follow-up)

Dockerfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,18 @@ USER kaiban
5858
ARG PORT=3000
5959
ENV PORT=${PORT}
6060

61+
# Single image, role chosen at runtime (ADR-013): ROLE=gateway|worker.
62+
# gateway → HTTP / WebSocket / A2A front door (exposes /health)
63+
# worker → LLM-backed task-consuming agent pool (no HTTP surface)
64+
# Default is gateway so the HEALTHCHECK below is valid out of the box; worker
65+
# deployments override with `-e ROLE=worker` (compose/k8s set it explicitly).
66+
ARG ROLE=gateway
67+
ENV ROLE=${ROLE}
68+
6169
EXPOSE ${PORT}
6270

71+
# The /health probe only exists in the gateway role. Worker deployments MUST
72+
# override or disable this HEALTHCHECK (docker-compose / k8s do).
6373
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
6474
CMD wget -qO- http://localhost:${PORT}/health || exit 1
6575

EXAMPLES.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,11 @@ docker run -p 3000:3000 \
407407
kaiban-distributed:latest
408408
```
409409

410-
Submit tasks:
410+
Submit tasks (A2A v0.3 `message/send`; target agent in `metadata.agentId`):
411411
```bash
412412
curl -X POST http://localhost:3000/a2a/rpc \
413413
-H 'Content-Type: application/json' \
414-
-d '{"jsonrpc":"2.0","id":1,"method":"tasks.create","params":{"agentId":"researcher","instruction":"Research AI agent trends 2025","expectedOutput":"A 200-word summary"}}'
414+
-d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"kind":"message","role":"user","messageId":"m1","parts":[{"kind":"text","text":"Research AI agent trends 2025"}],"metadata":{"agentId":"researcher","expectedOutput":"A 200-word summary"}}}}'
415415
```
416416

417417
---
@@ -421,20 +421,31 @@ curl -X POST http://localhost:3000/a2a/rpc \
421421
```bash
422422
# Unit tests (no Docker, all mocked)
423423
npm test
424-
#769 unit tests, 77 files, 100% coverage of src (+146 board tests via `cd board && npm test`)
424+
#108 unit-test files / 1155 tests, 100% coverage of src (board tests run separately via `cd board && npm test`)
425425

426426
# BullMQ E2E (Docker auto-starts Redis)
427427
npm run test:e2e
428-
#65 tests: task execution, fault tolerance, state sync, A2A protocol
428+
#69 tests: task execution, fault tolerance, state sync, A2A protocol, completion routing
429429

430430
# Kafka E2E (requires Kafka — starts automatically)
431431
npm run test:e2e:kafka
432432
# → 3 tests: publish-subscribe round-trip, unsubscribe
433433

434+
# Playwright visual baselines of the board + example viewers (needs the board dev server + gateway)
435+
cd board && npm run test:visual # add :update to regenerate baselines
436+
434437
# All quality gates at once
435438
npm run lint && npm run typecheck && npm run test:coverage
436439
```
437440

441+
> **Runaway-spend guard.** Both example orchestrators enforce a workflow-level
442+
> budget after **every** phase (blog-team: research/write/edit; global-research:
443+
> search/write/governance/editorial) and before each revision, via
444+
> `MAX_WORKFLOW_COST_USD` (default `0.50` in the example compose files) and
445+
> `MAX_WORKFLOW_TOKENS` (`0` = unlimited). On breach the workflow stops gracefully
446+
> (STOPPED) instead of draining the budget. This is separate from the per-agent
447+
> `MAX_TOKEN_BUDGET`.
448+
438449
---
439450

440451
## Example 5 — Monitor All Streams
@@ -560,7 +571,7 @@ interactive Human-in-the-Loop Approve / Revise / Reject controls.
560571
### Prerequisites
561572

562573
- Any kaiban-distributed gateway running (e.g. `./scripts/blog-team.sh start`)
563-
- Node.js ≥ 18 (board only; gateway still needs ≥ 22)
574+
- Node.js ≥ 20.19 (board uses Vite 8, which needs Node 20.19+/22.12+; gateway needs ≥ 22)
564575

565576
### Start
566577

0 commit comments

Comments
 (0)