@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.
- package/README.md +87 -90
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Grain
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
17
|
+
## 30-second version
|
|
12
18
|
|
|
13
19
|
```typescript
|
|
14
20
|
import { Grain } from "@reallyartificial/grain"
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
//
|
|
20
|
-
|
|
28
|
+
// Natural language system prompt, ready for any LLM
|
|
29
|
+
agent.toPrompt()
|
|
21
30
|
|
|
22
|
-
//
|
|
23
|
-
|
|
31
|
+
// Same agent, tuned for Slack
|
|
32
|
+
agent.toPrompt("slack")
|
|
24
33
|
|
|
25
|
-
// Clean YAML
|
|
26
|
-
|
|
34
|
+
// Clean YAML, stripped of metadata, usable directly as a prompt
|
|
35
|
+
agent.toString()
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
## What the personality numbers actually do
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
52
|
+
## Immutable by design
|
|
54
53
|
|
|
55
|
-
Every
|
|
54
|
+
Every operation returns a new Grain. The original never changes.
|
|
56
55
|
|
|
57
56
|
```typescript
|
|
58
|
-
const base = Grain.create("
|
|
59
|
-
const
|
|
57
|
+
const base = Grain.create("bot")
|
|
58
|
+
const friendly = base.setPersonality("warmth", 0.9)
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
base.personality.warmth // 0.5 (default, unchanged)
|
|
61
|
+
friendly.personality.warmth // 0.9
|
|
63
62
|
```
|
|
64
63
|
|
|
65
|
-
|
|
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)
|
|
72
|
-
const changes = a.diff(b)
|
|
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
|
-
##
|
|
74
|
+
## Works with any LLM
|
|
76
75
|
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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:
|
|
98
|
+
id: support-bot
|
|
88
99
|
version: 1.0.0
|
|
89
100
|
meta:
|
|
90
|
-
name:
|
|
91
|
-
description:
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
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/
|
|
39
|
+
"url": "https://github.com/reallyartificial/grain",
|
|
40
40
|
"directory": "sdk/typescript"
|
|
41
41
|
},
|
|
42
|
-
"homepage": "https://github.com/
|
|
42
|
+
"homepage": "https://github.com/reallyartificial/grain#readme",
|
|
43
43
|
"bugs": {
|
|
44
|
-
"url": "https://github.com/
|
|
44
|
+
"url": "https://github.com/reallyartificial/grain/issues"
|
|
45
45
|
},
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|