@reallyartificial/grain 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +87 -90
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # @reallyartificial/grain
1
+ # Grain
2
2
 
3
- TypeScript SDK for [Grain](https://github.com/anthropics/grain) — a structured data format for describing AI agents.
3
+ **Define AI agents as data, not prompts.**
4
+
5
+ Most agent frameworks treat personality and behavior as strings you paste into a system prompt. Grain treats them as a structured, immutable data type with real operations: build, merge, diff, query, serialize.
6
+
7
+ You define an agent's personality as numbers. Grain turns them into behavioral directives using [5-level graduated semantic anchoring](https://grain.reallyartificial.org). The same agent definition produces different prompts per channel.
8
+
9
+ [grain.reallyartificial.org](https://grain.reallyartificial.org) | [GitHub](https://github.com/reallyartificial/grain) | [PyPI (grain-sdk)](https://pypi.org/project/grain-sdk/)
4
10
 
5
11
  ## Install
6
12
 
@@ -8,130 +14,121 @@ TypeScript SDK for [Grain](https://github.com/anthropics/grain) — a structured
8
14
  npm install @reallyartificial/grain
9
15
  ```
10
16
 
11
- ## Usage
17
+ ## 30-second version
12
18
 
13
19
  ```typescript
14
20
  import { Grain } from "@reallyartificial/grain"
15
21
 
16
- // Load from file
17
- const agent = Grain.load("./my-agent.agent.yaml")
22
+ const agent = Grain.create("support-bot", { name: "Alex", description: "Customer support agent" })
23
+ .setPersonality("warmth", 0.9) // 0-1 float, maps to real behavioral directives
24
+ .setPersonality("formality", 0.3)
25
+ .setPersonality("confidence", 0.8)
26
+ .addBoundary({ description: "Never share internal pricing data", category: "data", enforcement: "hard", onViolation: "refuse" })
18
27
 
19
- // Generate system prompt
20
- const prompt = agent.toPrompt()
28
+ // Natural language system prompt, ready for any LLM
29
+ agent.toPrompt()
21
30
 
22
- // Channel-specific prompt
23
- const slackPrompt = agent.toPrompt("slack")
31
+ // Same agent, tuned for Slack
32
+ agent.toPrompt("slack")
24
33
 
25
- // Clean YAML for LLM consumption (strips metadata)
26
- const yaml = agent.toString()
34
+ // Clean YAML, stripped of metadata, usable directly as a prompt
35
+ agent.toString()
27
36
  ```
28
37
 
29
- ### Create programmatically
38
+ ## What the personality numbers actually do
30
39
 
31
- ```typescript
32
- import { Grain, Presets } from "@reallyartificial/grain"
33
-
34
- const agent = Grain.create("my-bot", { name: "My Bot", description: "Does helpful things" })
35
- .setPersonality("warmth", 0.8)
36
- .setPersonality("confidence", 0.9)
37
- .addRule({
38
- id: "always-greet",
39
- condition: { type: "always" },
40
- action: { type: "respond-with-style", style: { warmth: 0.9 } },
41
- })
42
- .addBoundary({
43
- description: "Never share internal data",
44
- category: "data",
45
- enforcement: "hard",
46
- onViolation: "refuse",
47
- })
48
- .addTool({ id: "search", usage: "Search knowledge base" })
49
-
50
- console.log(agent.toPrompt())
51
- ```
40
+ `setPersonality("warmth", 0.9)` doesn't just store `0.9`. It maps to a specific behavioral directive:
41
+
42
+ | Value | Directive |
43
+ |-------|-----------|
44
+ | 0.0-0.2 | "Be direct and clinical. Focus purely on facts and outcomes, not feelings." |
45
+ | 0.2-0.4 | "Be polite but task-focused." |
46
+ | 0.4-0.6 | "Be friendly. Show basic courtesy." |
47
+ | 0.6-0.8 | "Be warm and empathetic. Acknowledge feelings and show genuine care." |
48
+ | 0.8-1.0 | "Lead with empathy. Mirror the user's emotional state, use inclusive language." |
49
+
50
+ This works across 8 dimensions: formality, warmth, humor, assertiveness, verbosity, confidence, concreteness, urgency. Each has 5 graduated levels. The directives are grounded in the [SAC framework](https://arxiv.org/abs/2506.20993) for trait decomposition.
52
51
 
53
- ### Immutable mutations
52
+ ## Immutable by design
54
53
 
55
- Every method returns a new `Grain` the original is never modified.
54
+ Every operation returns a new Grain. The original never changes.
56
55
 
57
56
  ```typescript
58
- const base = Grain.create("my-bot")
59
- const withRule = base.addRule({ id: "r1", condition: { type: "always" }, action: { type: "require-confirmation" } })
57
+ const base = Grain.create("bot")
58
+ const friendly = base.setPersonality("warmth", 0.9)
60
59
 
61
- console.log(base.rules.length) // 0
62
- console.log(withRule.rules.length) // 1
60
+ base.personality.warmth // 0.5 (default, unchanged)
61
+ friendly.personality.warmth // 0.9
63
62
  ```
64
63
 
65
- ### Merge and diff
64
+ ## Merge and diff agents
66
65
 
67
66
  ```typescript
68
67
  const a = Grain.create("bot-a").setPersonality("warmth", 0.3)
69
68
  const b = Grain.create("bot-b").setPersonality("warmth", 0.9)
70
69
 
71
- const merged = a.merge(b) // b wins on conflicts
72
- const changes = a.diff(b) // { "voice.personality.warmth": { before: 0.3, after: 0.9 } }
70
+ const merged = a.merge(b) // b wins on conflicts
71
+ const changes = a.diff(b) // { "voice.personality.warmth": { before: 0.3, after: 0.9 } }
73
72
  ```
74
73
 
75
- ## CLI
74
+ ## Works with any LLM
76
75
 
77
- ```bash
78
- npx @reallyartificial/grain validate my-agent.agent.yaml
79
- npx @reallyartificial/grain generate my-agent.agent.yaml --channel slack
80
- npx @reallyartificial/grain info my-agent.agent.yaml
76
+ ```typescript
77
+ import { Grain } from "@reallyartificial/grain"
78
+ import OpenAI from "openai"
79
+
80
+ const agent = Grain.load("./support.agent.yaml")
81
+ const client = new OpenAI()
82
+
83
+ const response = await client.chat.completions.create({
84
+ model: "gpt-4o",
85
+ messages: [
86
+ { role: "system", content: agent.toPrompt() },
87
+ { role: "user", content: "I need help with my order" }
88
+ ]
89
+ })
81
90
  ```
82
91
 
83
- ## Minimal Spec
92
+ Swap OpenAI for Anthropic, Gemini, Ollama, or anything that takes a system prompt. Grain produces strings, not vendor lock-in.
93
+
94
+ ## Load from YAML
84
95
 
85
96
  ```yaml
86
97
  specVersion: "1.0"
87
- id: my-bot
98
+ id: support-bot
88
99
  version: 1.0.0
89
100
  meta:
90
- name: My Bot
91
- description: Does helpful things
101
+ name: Alex
102
+ description: Customer support agent
103
+ ```
104
+
105
+ That's a valid Grain file. Four required fields. Everything else has sensible defaults.
106
+
107
+ ```typescript
108
+ const agent = Grain.load("./support.agent.yaml")
109
+ ```
110
+
111
+ ## CLI
112
+
113
+ ```bash
114
+ npx @reallyartificial/grain validate agent.yaml
115
+ npx @reallyartificial/grain generate agent.yaml --channel slack
116
+ npx @reallyartificial/grain info agent.yaml
92
117
  ```
93
118
 
94
119
  ## API
95
120
 
96
- ### Constructors
97
- - `Grain.create(id, opts?)` — Create with defaults
98
- - `Grain.from(yamlOrJson)` Parse from string
99
- - `Grain.load(filePath)` Load from file
100
- - `Grain.of(spec)` — Wrap a plain AgentSpec object
101
-
102
- ### Mutations (return new Grain)
103
- - `addRule(rule)` / `removeRule(id)` / `addRules(rules)`
104
- - `addBoundary(b)` / `removeBoundary(desc)` / `addBoundaries(bs)`
105
- - `addTool(t)` / `removeTool(name)` / `addTools(ts)`
106
- - `addSkill(s)` / `removeSkill(name)` / `addSkills(ss)`
107
- - `addExpertise(domain, proficiency)` / `removeExpertise(domain)`
108
- - `setPersonality(dim, value)` — validates 0-1 range
109
- - `set(path, value)` / `get(path)` — generic deep access
110
- - `merge(other)` — deep merge, other wins
111
-
112
- ### Accessors
113
- - `id`, `name`, `version` — scalars
114
- - `personality`, `rules`, `boundaries`, `tools`, `skills`, `expertise` — copies
115
- - `data` — raw frozen AgentSpec
116
- - `isValid` — boolean
117
-
118
- ### Queries
119
- - `hasRule(id)`, `hasTool(name)`, `hasSkill(name)`
120
-
121
- ### Output
122
- - `toString(channel?)` — Clean YAML for LLMs (strips metadata)
123
- - `toPrompt(channel?)` — Natural language system prompt
124
- - `toYAML()` — Full YAML with all metadata
125
- - `toJSON()` — Full JSON
126
- - `validate()` — Returns `ValidationError[]`
127
- - `diff(other)` — Structural diff
128
-
129
- ### Presets
130
- - `Presets.personality.professional`
131
- - `Presets.personality.friendly`
132
- - `Presets.personality.expert`
133
- - `Presets.personality.creative`
134
- - `Presets.personality.executor`
121
+ **Constructors:** `Grain.create(id, opts?)` | `Grain.from(yamlOrJson)` | `Grain.load(filePath)` | `Grain.of(spec)`
122
+
123
+ **Mutations (return new Grain):**
124
+ `addRule` / `removeRule` / `addBoundary` / `removeBoundary` / `addTool` / `removeTool` / `addSkill` / `removeSkill` / `addExpertise` / `removeExpertise` / `setPersonality` / `set` / `get` / `merge`
125
+
126
+ **Output:**
127
+ `toString(channel?)` clean YAML | `toPrompt(channel?)` natural language | `toYAML()` full YAML | `toJSON()` full JSON | `validate()` | `diff(other)`
128
+
129
+ **Presets:** `Presets.personality.professional` | `.friendly` | `.expert` | `.creative` | `.executor`
130
+
131
+ Full API docs at [grain.reallyartificial.org](https://grain.reallyartificial.org)
135
132
 
136
133
  ## License
137
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reallyartificial/grain",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Grain — a structured data format for describing AI agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,12 +36,12 @@
36
36
  ],
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "https://github.com/anthropics/grain",
39
+ "url": "https://github.com/reallyartificial/grain",
40
40
  "directory": "sdk/typescript"
41
41
  },
42
- "homepage": "https://github.com/anthropics/grain#readme",
42
+ "homepage": "https://github.com/reallyartificial/grain#readme",
43
43
  "bugs": {
44
- "url": "https://github.com/anthropics/grain/issues"
44
+ "url": "https://github.com/reallyartificial/grain/issues"
45
45
  },
46
46
  "license": "MIT",
47
47
  "dependencies": {