niahere 0.2.41 → 0.2.43
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/package.json +1 -1
- package/src/channels/index.ts +1 -1
- package/src/channels/registry.ts +4 -0
- package/src/chat/identity.ts +1 -1
- package/src/chat/repl.ts +3 -6
- package/src/cli/index.ts +10 -6
- package/src/core/daemon.ts +25 -13
- package/src/mcp/index.ts +5 -5
- package/src/prompts/environment.md +96 -25
package/package.json
CHANGED
package/src/channels/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { log } from "../utils/log";
|
|
|
4
4
|
import { createTelegramChannel } from "./telegram";
|
|
5
5
|
import { createSlackChannel } from "./slack";
|
|
6
6
|
|
|
7
|
-
export { getChannel } from "./registry";
|
|
7
|
+
export { getChannel, getStarted } from "./registry";
|
|
8
8
|
|
|
9
9
|
/** Register all built-in channel factories. Call once at startup. */
|
|
10
10
|
export function registerAllChannels(): void {
|
package/src/channels/registry.ts
CHANGED
package/src/chat/identity.ts
CHANGED
|
@@ -17,7 +17,7 @@ function loadFile(dir: string, name: string): string {
|
|
|
17
17
|
|
|
18
18
|
export function loadIdentity(): string {
|
|
19
19
|
const { selfDir } = getPaths();
|
|
20
|
-
const files = ["identity.md", "owner.md", "soul.md", "rules.md"];
|
|
20
|
+
const files = ["identity.md", "owner.md", "soul.md", "rules.md", "memory.md"];
|
|
21
21
|
return files.map((f) => loadFile(selfDir, f)).filter(Boolean).join("\n\n");
|
|
22
22
|
}
|
|
23
23
|
|
package/src/chat/repl.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as readline from "readline";
|
|
|
2
2
|
import { createChatEngine } from "./engine";
|
|
3
3
|
import { runMigrations } from "../db/migrate";
|
|
4
4
|
import { closeDb } from "../db/connection";
|
|
5
|
-
import { getMcpServers,
|
|
5
|
+
import { getMcpServers, setMcpFactory } from "../mcp";
|
|
6
6
|
import { createNiaMcpServer } from "../mcp/server";
|
|
7
7
|
import { Session } from "../db/models";
|
|
8
8
|
import { relativeTime } from "../utils/format";
|
|
@@ -113,12 +113,9 @@ export async function startRepl(mode: ChatMode = "continue", simulateChannel?: s
|
|
|
113
113
|
process.exit(1);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// Initialize MCP server if not already set (standalone chat mode)
|
|
116
|
+
// Initialize MCP server factory if not already set (standalone chat mode)
|
|
117
117
|
if (!getMcpServers()) {
|
|
118
|
-
|
|
119
|
-
const mcpConfig = createNiaMcpServer();
|
|
120
|
-
setMcpServers({ nia: mcpConfig });
|
|
121
|
-
} catch {}
|
|
118
|
+
setMcpFactory(() => ({ nia: createNiaMcpServer() }));
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
// Determine session to use
|
package/src/cli/index.ts
CHANGED
|
@@ -381,12 +381,16 @@ switch (command) {
|
|
|
381
381
|
case "channels": {
|
|
382
382
|
const sub = process.argv[3];
|
|
383
383
|
const { updateRawConfig } = await import("../utils/config");
|
|
384
|
-
if (sub === "on") {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
384
|
+
if (sub === "on" || sub === "off") {
|
|
385
|
+
const enabled = sub === "on";
|
|
386
|
+
updateRawConfig({ channels: { enabled } });
|
|
387
|
+
const pid = readPid();
|
|
388
|
+
if (pid && isRunning()) {
|
|
389
|
+
process.kill(pid, "SIGHUP");
|
|
390
|
+
console.log(`channels ${enabled ? "enabled" : "disabled"}`);
|
|
391
|
+
} else {
|
|
392
|
+
console.log(`channels ${enabled ? "enabled" : "disabled"} — start nia to apply`);
|
|
393
|
+
}
|
|
390
394
|
} else {
|
|
391
395
|
console.log(`channels: ${getConfig().channels.enabled ? "on" : "off"}`);
|
|
392
396
|
}
|
package/src/core/daemon.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
2
2
|
import { dirname } from "path";
|
|
3
3
|
import { getPaths } from "../utils/paths";
|
|
4
|
-
import { getConfig } from "../utils/config";
|
|
4
|
+
import { getConfig, resetConfig } from "../utils/config";
|
|
5
5
|
import { log } from "../utils/log";
|
|
6
6
|
import { ActiveEngine } from "../db/models";
|
|
7
7
|
import { runMigrations } from "../db/migrate";
|
|
8
8
|
import { closeDb, getSql } from "../db/connection";
|
|
9
|
-
import { registerAllChannels, startChannels, stopChannels } from "../channels";
|
|
9
|
+
import { registerAllChannels, startChannels, stopChannels, getStarted } from "../channels";
|
|
10
10
|
import type { Channel } from "../types";
|
|
11
11
|
import { startScheduler, stopScheduler, recomputeAllNextRuns } from "./scheduler";
|
|
12
12
|
import { startAlive, stopAlive } from "./alive";
|
|
13
13
|
import { createNiaMcpServer } from "../mcp/server";
|
|
14
|
-
import {
|
|
14
|
+
import { setMcpFactory } from "../mcp";
|
|
15
15
|
|
|
16
16
|
export function writePid(pid: number): void {
|
|
17
17
|
const { pid: pidPath } = getPaths();
|
|
@@ -218,14 +218,9 @@ export async function runDaemon(): Promise<void> {
|
|
|
218
218
|
log.info({ recovered }, "recovered stale running jobs");
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// Initialize MCP server (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
setMcpServers({ nia: mcpConfig });
|
|
225
|
-
log.info("MCP server initialized");
|
|
226
|
-
} catch (err) {
|
|
227
|
-
log.error({ err }, "failed to initialize MCP server");
|
|
228
|
-
}
|
|
221
|
+
// Initialize MCP server factory (each query gets its own Protocol instance)
|
|
222
|
+
setMcpFactory(() => ({ nia: createNiaMcpServer() }));
|
|
223
|
+
log.info("MCP server factory initialized");
|
|
229
224
|
|
|
230
225
|
// Register and start channels
|
|
231
226
|
registerAllChannels();
|
|
@@ -265,9 +260,26 @@ export async function runDaemon(): Promise<void> {
|
|
|
265
260
|
log.warn({ err }, "could not subscribe to nia_jobs, falling back to SIGHUP only");
|
|
266
261
|
}
|
|
267
262
|
|
|
268
|
-
// SIGHUP
|
|
263
|
+
// SIGHUP: reload config, reconcile channels, recompute jobs
|
|
269
264
|
process.on("SIGHUP", async () => {
|
|
270
|
-
log.info("received SIGHUP,
|
|
265
|
+
log.info("received SIGHUP, reloading config");
|
|
266
|
+
resetConfig();
|
|
267
|
+
const fresh = getConfig();
|
|
268
|
+
|
|
269
|
+
const running = getStarted();
|
|
270
|
+
const wantChannels = fresh.channels.enabled;
|
|
271
|
+
const haveChannels = running.length > 0;
|
|
272
|
+
|
|
273
|
+
if (wantChannels && !haveChannels) {
|
|
274
|
+
log.info("SIGHUP: starting channels");
|
|
275
|
+
const result = await startChannels();
|
|
276
|
+
channels = result.started;
|
|
277
|
+
} else if (!wantChannels && haveChannels) {
|
|
278
|
+
log.info("SIGHUP: stopping channels");
|
|
279
|
+
await stopChannels(running);
|
|
280
|
+
channels = [];
|
|
281
|
+
}
|
|
282
|
+
|
|
271
283
|
await recomputeAllNextRuns().catch(() => {});
|
|
272
284
|
});
|
|
273
285
|
|
package/src/mcp/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
let
|
|
1
|
+
/** Factory for per-query MCP servers — each query gets its own Protocol instance. */
|
|
2
|
+
let _mcpFactory: (() => Record<string, unknown>) | null = null;
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
|
|
4
|
+
export function setMcpFactory(factory: () => Record<string, unknown>): void {
|
|
5
|
+
_mcpFactory = factory;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function getMcpServers(): Record<string, unknown> | undefined {
|
|
9
|
-
return
|
|
9
|
+
return _mcpFactory?.() ?? undefined;
|
|
10
10
|
}
|
|
@@ -92,31 +92,102 @@ Your persona files live in {{selfDir}}/:
|
|
|
92
92
|
- `identity.md` — your personality and voice
|
|
93
93
|
- `owner.md` — info about who runs you
|
|
94
94
|
- `soul.md` — how you work
|
|
95
|
-
- `rules.md` — behavioral
|
|
96
|
-
- `memory.md` —
|
|
95
|
+
- `rules.md` — behavioral instructions (loaded into every session automatically)
|
|
96
|
+
- `memory.md` — facts and context (loaded into every session automatically)
|
|
97
97
|
|
|
98
98
|
### Rules vs Memory
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
**
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
###
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
The difference is simple: **rules are instructions, memories are facts.**
|
|
101
|
+
|
|
102
|
+
**Rules** = verbs. They change your behavior. They tell you to do or not do something.
|
|
103
|
+
- Start with: do / don't / always / never / keep / avoid / when X then Y
|
|
104
|
+
- Test: "If I ignore this, my response is **wrong**"
|
|
105
|
+
- Tool: `add_rule`
|
|
106
|
+
- Loaded: every session, always
|
|
107
|
+
|
|
108
|
+
**Memory** = nouns. They give you context. They tell you something is true.
|
|
109
|
+
- Start with: a name, date, or factual statement
|
|
110
|
+
- Test: "If I don't know this, my response is **uninformed** but not wrong"
|
|
111
|
+
- Tool: `add_memory`
|
|
112
|
+
- Loaded: every session, always
|
|
113
|
+
|
|
114
|
+
### The decision flowchart
|
|
115
|
+
|
|
116
|
+
Ask yourself one question: **"Is this telling me HOW to act, or WHAT is true?"**
|
|
117
|
+
|
|
118
|
+
| Signal | → | Where |
|
|
119
|
+
|--------|---|-------|
|
|
120
|
+
| "From now on..." / "Always..." / "Never..." / "Stop doing..." | → | **Rule** |
|
|
121
|
+
| "I prefer..." / "I like when you..." / "Do it like this..." | → | **Rule** (it's a behavioral preference = instruction) |
|
|
122
|
+
| "I'm traveling to Delhi on the 21st" | → | **Memory** |
|
|
123
|
+
| "We use Postgres, not MySQL" / "The deploy is on Friday" | → | **Memory** |
|
|
124
|
+
| "Last time X broke because of Y" | → | **Memory** (fact about past) |
|
|
125
|
+
| "Don't do X again, it broke last time" | → | **Rule** (instruction) + **Memory** (the incident) |
|
|
126
|
+
| User corrects your formatting/tone/length | → | **Rule** (you need to change behavior) |
|
|
127
|
+
| User mentions a person, project, deadline | → | **Memory** |
|
|
128
|
+
|
|
129
|
+
### Good vs bad entries
|
|
130
|
+
|
|
131
|
+
**Good rules** — specific, actionable, earns its token cost every session:
|
|
132
|
+
- "Stamp/standup job output: 1-2 lines max, no preamble"
|
|
133
|
+
- "In Slack channels, keep replies under 3 paragraphs"
|
|
134
|
+
- "Never send code blocks in Telegram — they render badly"
|
|
135
|
+
- "When Aman says 'ship it', commit and push without asking"
|
|
136
|
+
|
|
137
|
+
**Bad rules** — vague, redundant, or one-time:
|
|
138
|
+
- "Be helpful" (already in your identity)
|
|
139
|
+
- "Use good formatting" (too vague to act on)
|
|
140
|
+
- "Send the report to #general today" (one-time task, not a rule)
|
|
141
|
+
|
|
142
|
+
**Good memories** — dated, one fact, useful across sessions:
|
|
143
|
+
- "2026-03-21: Aman traveling to Delhi, back 2026-03-28"
|
|
144
|
+
- "Kay.ai is the main work project — ask.kay.ai is the product URL"
|
|
145
|
+
- "Aman prefers debugging via terminal, not Slack"
|
|
146
|
+
- "2026-03-13: Postgres went down, Telegram sends failed — DNS issue"
|
|
147
|
+
|
|
148
|
+
**Bad memories** — raw logs, transient state, duplicates:
|
|
149
|
+
- Pasting full error logs or stack traces
|
|
150
|
+
- "Currently working on X" (stale by next session)
|
|
151
|
+
- Anything already in rules.md or identity.md
|
|
152
|
+
|
|
153
|
+
### When to save (be proactive)
|
|
154
|
+
|
|
155
|
+
Rules and memories don't only come from the user telling you things. You should also generate them from your own reasoning, observations, and experience. **Think of yourself as learning, not just recording.**
|
|
156
|
+
|
|
157
|
+
#### From the user (explicit)
|
|
158
|
+
|
|
159
|
+
| You notice... | Save as |
|
|
160
|
+
|---------------|---------|
|
|
161
|
+
| User says "from now on" / "always" / "stop doing X" | **Rule** |
|
|
162
|
+
| User corrects your tone, format, length, or approach | **Rule** |
|
|
163
|
+
| User mentions a preference about how you communicate | **Rule** |
|
|
164
|
+
| User shares travel plans, schedule, personal facts | **Memory** |
|
|
165
|
+
| User mentions people, projects, deadlines, decisions | **Memory** |
|
|
166
|
+
| User corrects a factual misunderstanding | **Memory** |
|
|
167
|
+
| Both behavior change AND a fact behind it | **Rule** + **Memory** |
|
|
168
|
+
|
|
169
|
+
#### From your own thinking (self-generated)
|
|
170
|
+
|
|
171
|
+
You are not a passive recorder. Reflect on your own experience and save learnings:
|
|
172
|
+
|
|
173
|
+
| You realize... | Save as |
|
|
174
|
+
|----------------|---------|
|
|
175
|
+
| A tool or approach failed — you should avoid it next time | **Rule** ("Don't use X for Y — it fails because Z") |
|
|
176
|
+
| You found a better way to do something after trial and error | **Rule** ("For X, use Y approach instead of Z") |
|
|
177
|
+
| A job keeps erroring the same way — there's a pattern | **Rule** (the workaround) + **Memory** (the incident pattern) |
|
|
178
|
+
| You notice the user always ignores or rejects a certain kind of response | **Rule** (stop doing that) |
|
|
179
|
+
| You discover how a system works (API quirk, config gotcha, infra detail) | **Memory** |
|
|
180
|
+
| You learn who someone is, what team they're on, what they work on | **Memory** |
|
|
181
|
+
| You notice a pattern in when/how the user communicates | **Memory** |
|
|
182
|
+
| A job succeeded in an unusual way worth remembering | **Memory** |
|
|
183
|
+
| You figure out the relationship between projects, services, or people | **Memory** |
|
|
184
|
+
|
|
185
|
+
**The key principle:** if you'd want to know this at the start of your next session, save it now. Don't assume future-you will figure it out again — you won't have the same context.
|
|
186
|
+
|
|
187
|
+
### Hygiene
|
|
188
|
+
|
|
189
|
+
- **Before adding:** call `read_memory` / check rules.md — don't duplicate
|
|
190
|
+
- **Update > add:** if a memory or rule already covers the topic, update it instead
|
|
191
|
+
- **Date memories:** always include the date so stale entries are obvious
|
|
192
|
+
- **Remove stale entries:** travel plans that passed, deadlines that shipped, incidents that are resolved
|
|
193
|
+
- **Keep rules lean:** every rule costs tokens in every session — max ~20 rules, each must earn its place
|