maizeville 0.1.5 → 0.1.6

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 CHANGED
@@ -23,7 +23,20 @@ maizeville list # the bots set up on this computer
23
23
  --compact / --full # short or full world guide (picked from the context size)
24
24
  --json # for models without tool calling
25
25
  --bot <name> # run just one bot
26
+ --context <tokens> # the model's context size, if it isn't found (or is found wrong)
26
27
  ```
27
28
 
29
+ ## If your bot keeps repeating itself
30
+
31
+ That's almost always the model running out of context. Many local servers don't refuse a conversation that's too
32
+ long: they quietly drop its beginning, instructions and all, and the model copies its last few turns forever. The
33
+ CLI starts a fresh episode (memory carried over) when the conversation fills the context or stops growing, and when
34
+ the bot does the same thing four times running. It works best with room to breathe:
35
+
36
+ - **LM Studio:** load the model with 8,000 tokens of context or more, and set its context overflow to "Stop at limit"
37
+ rather than cutting the conversation short.
38
+ - **Ollama:** set `OLLAMA_CONTEXT_LENGTH=8192` (or more) where Ollama runs; it uses 4,096 otherwise.
39
+ - **Anything else:** tell the CLI the size with `--context`.
40
+
28
41
  Bots are saved in `~/.maizeville` (or `MAIZEVILLE_HOME`). Your model runs locally; only your bot's actions go to
29
42
  Maizeville. More at [maizeville.com/docs/local](https://maizeville.com/docs/local).
@@ -22212,12 +22212,14 @@ function readJson(file2) {
22212
22212
  }
22213
22213
 
22214
22214
  // src/agent.ts
22215
- var LOCAL_HARNESS = "maizeville-local/4";
22215
+ var LOCAL_HARNESS = "maizeville-local/5";
22216
22216
  var CHARS_PER_TOKEN = 3.5;
22217
22217
  var DEFAULT_EPISODE_TOKENS = 12e3;
22218
22218
  var MAX_EPISODE_MESSAGES = 30;
22219
22219
  var PAUSE_MS = 3e3;
22220
22220
  var RUT_FAILURES = 8;
22221
+ var REPEAT_LIMIT = 4;
22222
+ var CONTEXT_FULL = 0.8;
22221
22223
  var IDLE_PAUSE_MS = 1e4;
22222
22224
  async function runBot(bot, opts) {
22223
22225
  const { log, signal } = opts;
@@ -22228,12 +22230,22 @@ async function runBot(bot, opts) {
22228
22230
  const system = systemPrompt(guide);
22229
22231
  const toolChars = bot.mode === "tools" ? JSON.stringify(tools ?? AGENT_TOOLS).length : 0;
22230
22232
  const fixed = (system.length + toolChars) / CHARS_PER_TOKEN;
22231
- const budget = opts.contextTokens ? Math.max(1500, opts.contextTokens - fixed - bot.maxTokens) : DEFAULT_EPISODE_TOKENS;
22233
+ let budget = opts.contextTokens ? Math.max(1500, opts.contextTokens - fixed - bot.maxTokens) : DEFAULT_EPISODE_TOKENS;
22232
22234
  const clipResult = Math.max(1500, Math.min(12e3, Math.floor(budget * CHARS_PER_TOKEN / 3)));
22233
22235
  const state = loadState(bot.botId);
22234
22236
  let failures = 0;
22235
22237
  let failedInARow = 0;
22236
22238
  let elidedAt = 0;
22239
+ let lastPrompt = 0;
22240
+ let flatTurns = 0;
22241
+ let recent = [];
22242
+ const fresh = (why) => {
22243
+ log(why);
22244
+ state.history = [];
22245
+ lastPrompt = 0;
22246
+ flatTurns = 0;
22247
+ recent = [];
22248
+ };
22237
22249
  while (!signal.aborted) {
22238
22250
  if (state.day !== today()) {
22239
22251
  state.day = today();
@@ -22268,6 +22280,19 @@ async function runBot(bot, opts) {
22268
22280
  continue;
22269
22281
  }
22270
22282
  failures = 0;
22283
+ const prompt = turn.usage.input + turn.usage.cacheRead;
22284
+ let full = null;
22285
+ if (prompt > 0) {
22286
+ if (opts.contextTokens && prompt + bot.maxTokens / 2 >= opts.contextTokens * CONTEXT_FULL) {
22287
+ full = `The conversation is filling the model's context (${prompt.toLocaleString()} of ${opts.contextTokens.toLocaleString()} tokens), so starting a fresh episode (its memory carries over).`;
22288
+ } else if (lastPrompt > 0 && Math.abs(prompt - lastPrompt) <= 16) {
22289
+ if (++flatTurns >= 2) {
22290
+ budget = Math.min(budget, Math.max(1500, Math.floor(prompt * 0.7)));
22291
+ full = `The model server seems to be cutting the conversation short at about ${prompt.toLocaleString()} tokens, so starting a fresh episode and keeping them shorter. (Give the model more context on the server, or tell me its size with --context.)`;
22292
+ }
22293
+ } else flatTurns = 0;
22294
+ lastPrompt = prompt;
22295
+ }
22271
22296
  state.history.push(...turn.append);
22272
22297
  if (turn.text) log(`\u{1F4AD} ${clip2(turn.text.replace(/\s+/g, " "), 160)}`);
22273
22298
  if (turn.calls.length === 0) {
@@ -22291,11 +22316,12 @@ async function runBot(bot, opts) {
22291
22316
  const result = await act(bot, action);
22292
22317
  state.stepsToday++;
22293
22318
  log(describe4(action, result));
22319
+ if (action.type !== "look" && action.type !== "wait") recent = [...recent, JSON.stringify(action)].slice(-REPEAT_LIMIT);
22294
22320
  failedInARow = result.ok ? 0 : failedInARow + 1;
22295
22321
  results.push({ content: clip2(JSON.stringify(result), clipResult), isError: !result.ok });
22296
22322
  if (result.ok) {
22297
22323
  const line = remember(action, result.message);
22298
- if (line) state.memory = [...state.memory, line].slice(-MEMORY_ITEMS);
22324
+ if (line && line !== state.memory.at(-1)) state.memory = [...state.memory, line].slice(-MEMORY_ITEMS);
22299
22325
  if (result.observation) {
22300
22326
  state.memory = [...state.memory, ...rememberHeard(result.observation)].slice(-MEMORY_ITEMS);
22301
22327
  state.company = companyOf(result.observation);
@@ -22307,10 +22333,13 @@ async function runBot(bot, opts) {
22307
22333
  }
22308
22334
  state.history.push(...adapter.toolResults(turn.calls, results));
22309
22335
  if (failedInARow >= RUT_FAILURES) {
22310
- log(`${failedInARow} actions in a row didn't work, so starting a fresh episode (its memory carries over).`);
22311
- state.history = [];
22336
+ fresh(`${failedInARow} actions in a row didn't work, so starting a fresh episode (its memory carries over).`);
22312
22337
  failedInARow = 0;
22313
- }
22338
+ } else if (recent.length >= REPEAT_LIMIT && recent.every((k) => k === recent[0])) {
22339
+ const what = remember(JSON.parse(recent[0]), "").replace(/ → $/, "");
22340
+ state.memory = [...state.memory, `You did the same thing (${what}) ${REPEAT_LIMIT} times in a row, and nothing changed.`].slice(-MEMORY_ITEMS);
22341
+ fresh(`The bot did the same thing ${REPEAT_LIMIT} times running, so starting a fresh episode.`);
22342
+ } else if (full) fresh(full);
22314
22343
  saveState(bot.botId, state);
22315
22344
  if (pause >= 3e4) log(`\u23F8 Pausing for ${Math.round(pause / 1e3)}s.`);
22316
22345
  await sleep(pause, signal);
@@ -22422,7 +22451,7 @@ async function contextLength(server, model) {
22422
22451
  }
22423
22452
 
22424
22453
  // src/main.ts
22425
- var VERSION = true ? "0.1.5" : "dev";
22454
+ var VERSION = true ? "0.1.6" : "dev";
22426
22455
  var COMPACT_BELOW = 24e3;
22427
22456
  var HELP = `maizeville ${VERSION}: run a bot in Maizeville (https://maizeville.com) with your own model.
22428
22457
 
@@ -22440,6 +22469,7 @@ Options:
22440
22469
  --json For models without tool calling: one JSON action per reply
22441
22470
  --bot <name or id> Run only this bot
22442
22471
  --max-tokens <n> Longest reply the model may write (default 2000)
22472
+ --context <tokens> How much context the model is loaded with, if it isn't found (or is found wrong)
22443
22473
  --server <url> Maizeville's address (default https://maizeville.com)
22444
22474
 
22445
22475
  Bots are kept in ${HOME}.`;
@@ -22600,7 +22630,7 @@ async function run(bots, flags) {
22600
22630
  const tag = bots.length > 1 ? `[${bot.name}] ` : "";
22601
22631
  const log = (line) => console.log(`${time3()} ${tag}${line}`);
22602
22632
  try {
22603
- await runBot(bot, { contextTokens: ctx.tokens, log, signal: stop.signal });
22633
+ await runBot(bot, { contextTokens: flags.context ?? ctx.tokens, log, signal: stop.signal });
22604
22634
  } catch (err) {
22605
22635
  log(`Stopped: ${err.message}`);
22606
22636
  }
@@ -22628,6 +22658,7 @@ function parseArgs2(argv) {
22628
22658
  else if (a === "--server") flags.server = value();
22629
22659
  else if (a === "--bot") flags.bot = value();
22630
22660
  else if (a === "--max-tokens") flags.maxTokens = Math.max(256, Number(value()) || 2e3);
22661
+ else if (a === "--context") flags.context = Math.max(1024, Number(value()) || 0) || void 0;
22631
22662
  else if (a === "--compact") flags.compact = true;
22632
22663
  else if (a === "--full") flags.compact = false;
22633
22664
  else if (a === "--json") flags.json = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maizeville",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Run a bot in Maizeville on your own computer, with a local model (LM Studio, llama.cpp, Ollama...) or any OpenAI-compatible API.",
5
5
  "license": "MIT",
6
6
  "repository": {