Skip to content

Commit 290f601

Browse files
committed
v0.2.0: runtime, CLI, BYOK adapters, phase-loop enforcement
OpenWar evolves from a system-prompt overlay into a runnable runtime. The framework doc stays canonical; the runtime adds rails that make the framework's rules enforceable instead of advisory. Foundation: - Zero runtime dependencies. devDeps: typescript, tsx, @types/node. - Strict ESM/NodeNext TypeScript. - node:test runner via tsx for the test suite. Brief layer: - Hand-rolled YAML subset parser (no js-yaml dep). - parseBrief, validateBrief, renderBriefForAgent, generateBriefId. - brief_id autogen as YYYY-MM-DD-<6-char-random> when missing. Detector suite (regex/pattern only, no LLM judging): - confirmation-summary: detects Phase 0 marker or section headings. - blocker: explicit Phase 2 marker plus heuristic phrases. - destructive: action-intent verbs with negation guard, cross-checks brief.authorized_costs for pre-approval. - banned-phrases: voice-rule warnings (Certainly, leverage, etc.). - phase-marker, completion. - All detectors are code-fence aware so docs about the rules don't trip them. Adapters (BYOK via env vars, streaming via native fetch + SSE): - Anthropic, OpenAI, Gemini, Grok, OpenAI-compat (OpenRouter, Groq, Together, Ollama, vLLM, llama.cpp). - MockAdapter for deterministic tests. State: - JSON session files plus append-only JSONL transcripts at ~/.openwar/sessions/. - Atomic writes via tmp+rename. - Schema-version gated. Phase loop: - intake -> execute -> {blocker / destructive / completion}. - Destructive denial loop records denial as user msg and re-invokes execute. - Auto-pilot and gated modes both wired. - max_steps=30 failsafe. CLI: - run, resume, list, inspect [--transcript], validate, adapters, version, --help. - Bin shim handles both compiled-dist and tsx-from-source paths. Examples: - creative-brief.md (gated, generation_credits authorized). - engineering-brief.md (auto, scope-locked, filesystem_write authorized). - examples/README.md explains how to run each. Tests: 31 passing across brief, detectors, runner. CI: GitHub Actions matrix (Ubuntu/macOS/Windows x Node 20/22) running lint -> build -> test. Bug caught during build: destructive detector matched negated intent ('I will not force-push'), creating an infinite Phase-3-denial loop. Added a negation guard plus a regression test.
1 parent 071c9a6 commit 290f601

49 files changed

Lines changed: 4532 additions & 44 deletions

Some content is hidden

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

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, macos-latest, windows-latest]
16+
node-version: [20.x, 22.x]
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: ${{ matrix.node-version }}
22+
- name: Install
23+
run: npm install --no-audit --no-fund
24+
- name: Lint (em dashes)
25+
run: npm run lint:em-dashes
26+
- name: Lint (sanity)
27+
run: npm run lint:sanity
28+
- name: Build
29+
run: npm run build
30+
- name: Test
31+
run: npm test

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@
44
.idea/
55
.vscode/
66
node_modules/
7+
dist/
8+
*.log
9+
.env
10+
.env.local
11+
.openwar/
12+
coverage/
13+
*.tsbuildinfo
14+
DEFERRED.md

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Changelog
2+
3+
## 0.2.0
4+
5+
Runtime release. The framework gets a real engine.
6+
7+
### Added
8+
9+
- Node / TypeScript package with a `openwar` CLI.
10+
- Brief parser and validator (markdown + frontmatter), with kebab-case project slugs, optional `brief_id` autogen, scope-lock and mode overrides, and an `authorized_costs` list that pre-approves categories of destructive actions.
11+
- Phase-aware runner enforcing Phase 0 (Confirmation Summary required), Phase 1 (gated or auto-pilot execution), Phase 2 (blocker halt + persist + resume), Phase 3 (destructive flag, explicit per-session approval), and Phase 4 (canonical completion report).
12+
- Deterministic detectors for confirmation, blocker, destructive intent, banned phrases, phase markers, and completion. Regex / pattern based; no second LLM.
13+
- BYOK adapters: Anthropic Claude, OpenAI, Google Gemini, xAI Grok, and an OpenAI-compatible adapter that handles OpenRouter / Groq / Together / Ollama / vLLM / llama.cpp.
14+
- MockAdapter for deterministic offline tests.
15+
- Session persistence at `~/.openwar/sessions/` (one JSON per session + JSONL append-only transcript). `openwar resume <brief_id>` reopens.
16+
- CLI commands: `run`, `resume`, `list`, `inspect`, `validate`, `adapters`, `version`.
17+
- Library surface: `run`, `parseBrief`, `validateBrief`, all adapters, all detectors, and state helpers exported from the package root so integrators (e.g. War Room) can embed the runtime.
18+
- Examples: a gated creative brief and an auto-pilot engineering brief.
19+
- Cross-platform CI (Ubuntu, macOS, Windows on Node 20 and 22) with em-dash and personal-data sanity lints in front of the build / test pipeline.
20+
21+
### Changed
22+
23+
- `openwar.md` clarified: OpenWar is now both a framework (the markdown doc) and a runtime (this package). The "not a runtime" sentence in v0.1 has been removed.
24+
25+
### Notes for War Room integrators
26+
27+
The library surface is stable for v0.2. Re-exporting from `openwar` (root) gives you everything War Room needs to swap its raw adapter path for the OpenWar phase loop in channels where the operator selects the framework.
28+
29+
## 0.1.0
30+
31+
Initial release. System-prompt-only framework. Single markdown file (`openwar.md`), reference brief template, MIT license.

README.md

Lines changed: 115 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,80 +4,154 @@
44

55
<h1 align="center">OpenWar</h1>
66

7-
<p align="center"><strong>The framework that ships in <a href="https://github.com/pythonluvr/war-room">War Room</a>. Use it anywhere.</strong></p>
7+
<p align="center"><strong>A framework and a runtime for agent behavior that doesn't go off the rails.</strong></p>
88

9-
OpenWar is a system prompt that turns any LLM-based agent (Claude, GPT, Gemini, others) into something that behaves like a senior peer instead of a customer service rep. It confirms briefs before acting, breaks work into phases, asks before doing anything destructive, and writes like an adult who's busy.
9+
OpenWar replaces eager-customer-service-rep defaults with the behavior of a senior peer. It confirms briefs before acting, breaks work into phases, asks before doing anything destructive, and writes like an adult who's busy.
1010

11-
It's a single markdown file. Drop it into your agent's system prompt, Claude Code's `CLAUDE.md`, Cursor's settings, Hermes config, OpenClaw skills, anywhere, and your agent's behavior changes.
11+
You can use it two ways:
1212

13-
## What's in it
13+
1. **As a runtime** (new in v0.2). Install the package, point it at a brief, watch the phase machine enforce the framework against any BYOK LLM. The runtime is opinionated: no flag to disable Phase 3, no way to skip the Confirmation Summary.
14+
2. **As a system prompt** (v0.1, still supported). Paste [`openwar.md`](./openwar.md) into Claude Code's CLAUDE.md, Cursor's rules, Hermes, OpenClaw, or anywhere else. The agent's behavior changes; nothing else does.
1415

15-
- **Phase architecture**: every task moves through Brief Intake → Execution → (Blocker / Destructive Flag) → Completion. Operators can interrupt at any phase.
16-
- **Confirmation Summary**: agent never starts work without acknowledging what it heard.
17-
- **Per-step vs auto-pilot mode**: operators pick how much gating they want; agents respect it.
18-
- **Hard rules against** invented next steps, hallucinated capabilities, unauthorized actions, blind retries past blockers.
19-
- **Pre-mortem trigger** for strategic / problem-solving / creative / cost-spending work.
20-
- **Voice rules**: banned filler phrases ("Certainly", "Absolutely", "leverage"...), peer-level tone.
21-
- **"Best solution, not the fast one"**: agent leads with the gold-standard path by default, not stopgaps.
16+
The framework doc and the runtime are the same source of truth. The doc tells the model what to do. The runtime makes sure it actually does it.
2217

23-
Read the full framework: [`openwar.md`](./openwar.md).
18+
## Quick start (runtime)
2419

25-
## Install
26-
27-
### Claude Code
20+
```bash
21+
npx pythonluvr/openwar run examples/creative-brief.md --adapter anthropic
22+
```
2823

29-
Append OpenWar to your global CLAUDE.md so every Claude Code session loads it automatically:
24+
Or install:
3025

3126
```bash
32-
mkdir -p ~/.claude
33-
curl -fsSL https://raw.githubusercontent.com/pythonluvr/openwar/main/openwar.md >> ~/.claude/CLAUDE.md
27+
npm install -g pythonluvr/openwar
28+
export ANTHROPIC_API_KEY=...
29+
openwar run examples/engineering-brief.md
3430
```
3531

36-
Or for a single project:
32+
Provide an API key for whichever adapter you pick:
33+
34+
| Adapter | Env var | Default model |
35+
|-----------------|--------------------------|-----------------------|
36+
| `anthropic` | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` |
37+
| `openai` | `OPENAI_API_KEY` | `gpt-4o` |
38+
| `gemini` | `GEMINI_API_KEY` | `gemini-2.0-flash` |
39+
| `grok` | `XAI_API_KEY` | `grok-2-latest` |
40+
| `openai-compat` | `OPENAI_COMPAT_API_KEY` | (specify with `--base-url`) |
41+
42+
`openai-compat` covers OpenRouter, Groq, Together, Ollama, vLLM, llama.cpp, and anything else speaking OpenAI's chat-completions protocol.
43+
44+
## What the runtime enforces
45+
46+
| Phase | What happens | What blocks |
47+
|---------|--------------------------------------------------------------------------------------------------|-------------|
48+
| Phase 0 | Agent must produce a Confirmation Summary with Objective / Deliverables / Constraints / Tools / Unknowns. | No execution until the operator accepts. |
49+
| Phase 1 | Agent executes step by step. Gated mode pauses between steps; auto-pilot runs through clean ones. | Banned phrases warn. |
50+
| Phase 2 | If the agent declares it's blocked, the runtime halts the session and persists state. | Resume with `openwar resume <brief_id>`. |
51+
| Phase 3 | If the agent announces intent to do something destructive or out-of-directive, the runtime stops and asks for explicit yes. | Authorization can be pre-approved per category in the brief's `authorized_costs`. |
52+
| Phase 4 | Agent produces a concise completion report. | None. |
53+
54+
The phase loop is enforced by deterministic pattern detectors. No second LLM, no judging. If the agent skips the Confirmation Summary, the runtime asks it to restate before letting execution start.
55+
56+
## CLI
57+
58+
```text
59+
openwar run <brief.md> [--adapter <id>] [--model <name>] [--mode gated|auto]
60+
[--resume] [--ephemeral]
61+
openwar resume <brief_id>
62+
openwar list
63+
openwar inspect <brief_id> [--transcript]
64+
openwar validate <brief.md>
65+
openwar adapters
66+
openwar version
67+
```
3768

38-
```bash
39-
curl -fsSL https://raw.githubusercontent.com/pythonluvr/openwar/main/openwar.md >> ./CLAUDE.md
69+
Sessions persist to `~/.openwar/sessions/` as JSON + JSONL transcript. Resume with `openwar resume <brief_id>`. `--ephemeral` skips persistence (used by tests and integrators).
70+
71+
## Use as a library
72+
73+
```ts
74+
import { run, MockAdapter, AnthropicAdapter } from "openwar";
75+
76+
const adapter = new AnthropicAdapter({ id: "anthropic", model: "claude-sonnet-4-6" });
77+
const result = await run({ briefPath: "./brief.md", adapter });
78+
79+
if (!result.completed) {
80+
console.error(`Halted at ${result.final_phase}: ${result.halt_reason}`);
81+
}
4082
```
4183

42-
### Cursor
84+
War Room and other integrators consume OpenWar this way; the runtime is the framework's enforcement surface.
4385

44-
Cursor → Settings → Rules for AI → paste the contents of [`openwar.md`](./openwar.md).
86+
## Brief format
4587

46-
### War Room
88+
Briefs are markdown with required frontmatter. A reference template ships at [`templates/brief.md`](./templates/brief.md).
4789

48-
War Room ships OpenWar as the bundled default framework. Toggle in `Settings → Agent → Framework`. Nothing to install, it's already there.
90+
```yaml
91+
---
92+
project: <slug> # required, kebab-case
93+
brief_id: YYYY-MM-DD-NNN # optional; auto-generated if absent
94+
deadline: YYYY-MM-DD # optional
95+
scope_locked: true|false # if true, refuse out-of-scope additions
96+
mode: gated|auto # optional override of per-step-vs-auto
97+
authorized_costs: # pre-approves these destructive categories
98+
- filesystem_write
99+
- generation_credits
100+
- git_push
101+
---
102+
```
49103

50-
### Anything else (Hermes, OpenClaw, custom)
104+
Recognized `authorized_costs` categories include `filesystem_delete`, `git_history_rewrite`, `git_push`, `deploy`, `external_message`, `paid_api`, `package_change`, `ci_modify`, `process_kill`. Use `*` to authorize everything (rarely a good idea).
51105

52-
Whatever your runtime calls "system prompt" or "preamble", paste the contents of `openwar.md` in.
106+
Body sections: **Objective**, **Deliverables**, **Constraints**, **Tools required**, **Notes / unknowns**.
53107

54-
## Why does this exist
108+
Validate a brief without running it:
55109

56-
Default agent behavior (eager, sycophantic, prone to surprise actions) produces bad outcomes for serious work. You ship the wrong thing because the agent didn't confirm what you meant. You discover paid API spend you didn't authorize. You get "I'd be happy to help you with that!" instead of an actual answer.
110+
```bash
111+
openwar validate brief.md
112+
```
113+
114+
## Still want just the system prompt?
115+
116+
Paste [`openwar.md`](./openwar.md) into your agent's system prompt. The framework remains a single markdown file. You lose enforcement (the model is on the honor system), but you keep the behavior overlay.
117+
118+
### Claude Code
119+
120+
```bash
121+
mkdir -p ~/.claude
122+
curl -fsSL https://raw.githubusercontent.com/pythonluvr/openwar/main/openwar.md >> ~/.claude/CLAUDE.md
123+
```
124+
125+
### Cursor
126+
127+
Settings → Rules for AI → paste the contents of `openwar.md`.
128+
129+
### War Room
57130

58-
OpenWar replaces those defaults with the behavior of a senior peer. The agent reads your brief, says "here's what I heard, confirm?", and waits. It writes like an adult. It stops when it hits something it can't resolve instead of guessing. It tells you when something is outside what you asked for.
131+
War Room ships OpenWar as its default framework. v0.5+ adopts the runtime; earlier versions used the system prompt only.
59132

60-
It's not magic. It's a system prompt. But it's a system prompt that's been battle-tested across thousands of real freelance / dev / creative tasks and refined to remove the failure modes that kept showing up.
133+
## Why both
61134

62-
## Variants
135+
Behavioral overlays are easy to ignore. A model that's been told "always produce a Confirmation Summary" will sometimes skip it under context pressure or specific phrasing. The runtime catches the skip and asks the model to restate.
63136

64-
The default is `openwar.md`, the full framework with all phases, hard rules, and triggers. Future variants will live in [`variants/`](./variants/):
137+
System prompts cost nothing to install and work with any runtime. The runtime is heavier, but it actually enforces the rules.
65138

66-
- `openwar-strict.md`: adds aggressive confirmation gating; useful for high-stakes / production work
67-
- `openwar-light.md`: drops the pre-mortem trigger and brief format requirement; useful for casual chat / quick questions
139+
## Versioning
68140

69-
(Empty for now, ships when stable.)
141+
Current: **v0.2.0** (runtime + framework doc).
70142

71-
## Templates
143+
- v0.2: runtime, CLI, BYOK adapters for Anthropic / OpenAI / Gemini / Grok / OpenAI-compat.
144+
- v0.3: CLI-bridge adapters (claude-cli, codex-cli), MCP tool calling.
145+
- v0.4: multi-agent / boardroom orchestration.
72146

73-
[`templates/brief.md`](./templates/brief.md) is a reference brief in OpenWar's format. Copy it when starting new tasks; the frontmatter fields drive how the agent gates execution.
147+
Framework doc is versioned with the package. Drop-in upgrades preserve compatibility within a major version; major bumps may rename phases or change the brief format.
74148

75149
## License
76150

77-
[MIT](./LICENSE). Use it, modify it, fork it, ship your own variants, paste it into commercial products. No obligations beyond keeping the copyright notice.
151+
[MIT](./LICENSE).
78152

79153
## Authorship
80154

81-
OpenWar is the framework that ships inside [War Room](https://github.com/pythonluvr/war-room), authored over many iterations of running real agent work. This standalone repo exists so people who don't use War Room can still adopt the framework.
155+
OpenWar is the framework that ships inside [War Room](https://github.com/pythonluvr/war-room), authored across many iterations of running real agent work. This standalone repo exists so people who don't use War Room can still adopt the framework.
82156

83-
Issues + PRs welcome.
157+
Issues and PRs welcome.

bin/openwar

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env node
2+
// Resolves whether to run compiled dist (preferred) or tsx-compile src on the
3+
// fly (for development inside the repo before `npm run build` has run).
4+
5+
import { existsSync } from "node:fs";
6+
import { fileURLToPath, pathToFileURL } from "node:url";
7+
import { dirname, join, resolve } from "node:path";
8+
9+
const here = dirname(fileURLToPath(import.meta.url));
10+
const repoRoot = resolve(here, "..");
11+
const dist = join(repoRoot, "dist", "cli.js");
12+
const srcCli = join(repoRoot, "src", "cli.ts");
13+
14+
async function loadCli() {
15+
if (existsSync(dist)) {
16+
return await import(pathToFileURL(dist).href);
17+
}
18+
if (existsSync(srcCli)) {
19+
try {
20+
const tsx = await import("tsx/esm/api");
21+
tsx.register();
22+
return await import(pathToFileURL(srcCli).href);
23+
} catch {
24+
process.stderr.write(
25+
"openwar: dist/cli.js not found and tsx is not installed. Run `npm install` and `npm run build`.\n",
26+
);
27+
process.exit(1);
28+
}
29+
}
30+
process.stderr.write("openwar: cannot locate cli (no dist/cli.js or src/cli.ts).\n");
31+
process.exit(1);
32+
}
33+
34+
const mod = await loadCli();
35+
if (typeof mod.main !== "function") {
36+
process.stderr.write("openwar: cli module did not export main().\n");
37+
process.exit(1);
38+
}
39+
try {
40+
const code = await mod.main(process.argv.slice(2));
41+
process.exit(code ?? 0);
42+
} catch (err) {
43+
process.stderr.write(`openwar: ${(err && err.message) || err}\n`);
44+
process.exit(1);
45+
}

examples/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# OpenWar example briefs
2+
3+
Two reference briefs you can run end-to-end against any configured adapter.
4+
5+
## creative-brief.md
6+
7+
A copywriting task with gated execution. Demonstrates:
8+
9+
- Frontmatter with `scope_locked: false` and `mode: gated`.
10+
- An `authorized_costs: [generation_credits]` pre-approval so the agent does not flag every word it produces as destructive.
11+
- Soft constraints expressed in plain language (no em dashes, no filler openers).
12+
13+
Run it:
14+
15+
```bash
16+
npx openwar run examples/creative-brief.md --adapter anthropic
17+
```
18+
19+
In gated mode the runner pauses after each step. Press Enter or type `ok` to continue. Type `done` to stop early.
20+
21+
## engineering-brief.md
22+
23+
A code task with auto-pilot execution and a locked scope. Demonstrates:
24+
25+
- `scope_locked: true` so out-of-scope additions get refused without renegotiation.
26+
- `mode: auto` so the agent moves through clean steps without waiting on the operator.
27+
- `authorized_costs: [filesystem_write]` so writes do not trip the destructive flag, but git pushes, deploys, and process kills still do.
28+
29+
Run it:
30+
31+
```bash
32+
export OPENAI_API_KEY=sk-...
33+
npx openwar run examples/engineering-brief.md --adapter openai --model gpt-4o
34+
```
35+
36+
## Inspecting after the run
37+
38+
```bash
39+
openwar list # show recent sessions
40+
openwar inspect <brief_id> # show session metadata
41+
openwar inspect <brief_id> --transcript # full back-and-forth
42+
```
43+
44+
Sessions persist to `~/.openwar/sessions/`. Resume with `openwar resume <brief_id>`.

examples/creative-brief.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
project: launch-anthem
3+
brief_id: 2026-01-15-A1
4+
scope_locked: false
5+
mode: gated
6+
authorized_costs:
7+
- generation_credits
8+
---
9+
10+
# Objective
11+
12+
Draft three competing 30-second voiceover scripts for a product launch anthem. Tone should land between "we built this" pride and "you are seen" warmth. The product is a self-hosted journaling app for people who write longhand. The audience is everyone who has tried five productivity tools and quit them.
13+
14+
# Deliverables
15+
16+
- Three voiceover scripts of 60 to 80 words each
17+
- For each script: a one-line tonal description and a single sentence about who it targets
18+
- A recommendation at the end with which to film first and why
19+
20+
# Constraints
21+
22+
- No em dashes in any of the output
23+
- No filler openers like "in a world where" or "what if"
24+
- Do not write more than three options; do not pad with a fourth weaker variant
25+
- Stay vendor-neutral; do not name competing products
26+
27+
# Tools required
28+
29+
- None beyond text generation
30+
31+
# Notes / unknowns
32+
33+
- The product name has not been finalized. Use the placeholder "the app" instead of inventing one.
34+
- A second pass for VO timing markers is out of scope for this brief.

0 commit comments

Comments
 (0)