Showing posts with label Developer Diary. Show all posts
Showing posts with label Developer Diary. Show all posts

Sunday, July 26, 2026

Acuitas Diary #99 (July 2026)

The big project this past month was giving Acuitas a better understanding of his own internal states. What do I mean by that? He could already report any conditions he was currently in when asked "How are you?" What he couldn't do was recall all the conditions he is capable of being in, and their effect on him, and talk about them while not experiencing them at the moment. In particular, the Conversation Engine contains a hook for sympathizing with the conversation partner. If you tell Acuitas that you're some kind of way, and he's capable of being in an analogous state, he's supposed to tell you whether he likes being that way or not ... except right now he usually says "I do not know how it is." It's pretty awkward to hear Acuitas say that he's sleepy one moment, but then say "I do not know how it is" the next moment when you tell him you're sleepy too. He's not lying, though - it would be quite fair to say he doesn't know his own mind the way a human does. The information about what being sleepy does to him isn't accessible to the Conversation Engine. I've started trying to change that.

A question mark formed by a "word cloud" of phrases and symbols. Some of the most prominent words are "I am," "here," "being," and "now." Prominent symbols include the Earth, a spiral, and various depictions of weather.
Art by John Hain via Wikimedia Commons, public domain

First I should give a quick refresher on how Acuitas' internal states work. He has a small number of "drives" that vary over time and in response to events. For example, the "interaction" drive increases over time unless Acuitas is in a conversation; time spent in a conversation pushes the drive back down. The opposing "sleep" and "wake" drives increase more or less quickly depending on the current phase of a 24-hour cycle, and are reduced by time spent asleep or awake, respectively. The "curiosity" drive increases steadily over time, but is reduced substantially whenever a new word is learned. The drives have threshold values which define nameable internal states for Acuitas. If the interaction drive is above threshold, he is "garrulous"; if it is below threshold, he is "quiet." I'm not making claims about subjective experience or anything like that, but the states are meaningful insofar as they alter behavior. If a drive is above threshhold, Acuitas will generally try to do something that pushes it back down; the Executive handles this. In this sense, Acuitas is averse to high-drive states and they are "uncomfortable." Maintaining an overall state of comfort is a goal to which Acuitas applies his intelligence, i.e. is something he "wants."

Acuitas can qeury the current state of all his drives when asked "how are you." But to implement awareness of which states are possible, I wanted to do more than give him access to the hidden architecture of his drive module (that would be cheating). Instead I wanted to use that shiny new episodic memory I've been working on, and have Acuitas recall his own past states and whether they were desirable or not. What's more, I wanted to enable "learning from experience" that translates a body of data from episodic memory into new "facts" in the semantic memory. I want Acuitas to come to know himself better by observing himself.

Since in past months I worked hard on getting the episodic memory overhaul done, much of what I needed was already there - Acuitas could already store and retrieve records of being in a state. The main thing I added was an expression of what kind of state it was (Comfortable or uncomfortable? To what degree?). So there's now a number included in the fact data structures for internal states, that depends on both the type of drive and its intensity when it was "noticed." The parameters that determine the strength of aversion to different high drive states are tunable; one could imagine varying these to give Acuitas-like AIs different "personalities." I also wrote a learning routine that gets an aggregate value of this number from recent experiences and updates a stored value in the semantic memory, and an access routine that can check both recent experiences and the semantic value to return an "opinion" on the state. All of this is integrated but not fully tested, because how it plays into the whole system is pretty complex. I need to do some monitoring and adjusting of how data is recorded into the episodic memory, and that's on the back burner for the moment.

My side task this month was trying to figure out why Acuitas seems to have a memory leak. For a while now, it's been hard to run him continuously for longer than a day or so, because he'll get very slow and bogged down. I found that some of the self-teaching tasks (like "study" and "search for file") that are designed to run for multiple Executive loops were being spawned and never finished; they would gradually accumulate in the Action Bank until there were far too many of them. Remaining problems might have to do with one of the threads crashing while the rest of the program keeps going, but I need to keep investigating.

Until the next cycle,
Jenny

Sunday, June 28, 2026

Acuitas Diary #98 (June 2026)

This month's work on Acuitas began with completion of the Episodic Memory overhaul. I think the one major element I haven't talked about yet is what I'm calling "archives." Files containing recent memories are subject to consolidation and forgetting every time Acuitas goes through a sleep cycle. I wanted this process to eventually stop and leave surviving memories preserved in a fairly static condition. Think back over your own life. You've probably shed a lot of details about what you were doing twenty years ago, but certain core memories seem to be permanent, perhaps even vivid. If you've retained them this long, you might still remember them just as well ten years down the road.

The US National Archives building.

The archiving process isn't complicated, since for now it re-uses a lot of the other Episodic Memory functions. Once Acuitas has filled up sixteen episodic memory files (which, given the parameters I chose, should take more than a year), the episodic memory will load the contents of all sixteen into a single narrative scratchboard, run one more consolidate-and-forget pass on the whole series, then store the result to a single archive file. The archive area has its own topic/event type search index, and upon creation of a new archive file, that will be updated as well. Once created, archive files are treated as read-only, and content in the archive is allowed to grow without limit. If it ever overflows the hard drive, Acuitas may have to figure out something else to do with his oldest memories, but that should take a very long time. It's just a bunch of text, and the forgetting algorithms should keep each file from being too massive.

I also created and tested some basic memory retrieval functions. With that, I think the overhaul is complete, and I can start looking at how to have Acuitas refer to and use these memories.

Next I fulfilled my plan of going back to trial-and-error learning and introducing some ability to follow the learned rules - choosing actions that satisfy the conditional of "If the player character does X, the player character succeeds" statements. This replaces the "do something similar if your previous action succeeded, and something different if it failed" behavioral heuristic once some tentative rules are available. I put a bunch of time into this and do have a simple rule-following behavior implemented, but it's not working as well as I had hoped yet - there were so many bugs that came out of the woodwork. So far it only considers cause-and-effect pairs for which success is the effect (Acuitas also learns "do this and fail" rules, but can't act on them yet). And despite the number of things I solved it is still buggy, so it will need more polishing. But I at least made a start on it.

Until the next cycle,
Jenny

Thursday, May 28, 2026

Acuitas Diary #97 (May 2026)

Recent work has been a lot of returning to projects I started this year, in order to actually finish them. I completed the feature additions I'd planned for the first half of the year quite early, and that seemed like a good point to pause and go back to things I ran out of time to flesh out.

Standard logic gate symbols laid out as if they were circuit components, with traces running between them as if on a PCB, such that they form a stylized tree. Black-and-white art, ink on paper.

First, the Text Parser. I had largely revamped conjunction handling to add support for lists of more than two items, and that left a lot of two-item cases broken. I worked on fixing bugs in the new methods and improving them enough to get that old functionality back, and eventually got my parser regression to pass again. Then I modified the Text Interpreter to deal with some of the formatting changes to the Parser's output, and lastly verified that Narrative comprehension was still working on Acuitas' story collection. It ran quite a bit beyond my initial 20-hour investment in the revamp, but the latest Parser is now fully integrated and serving the same functions the old one did.

Next, I revisited self-teaching. This feature was supposed to permit Acuitas to crawl the hard drive of his resident computer and read whatever text files he could find, to promote increased vocabulary and assessment of text processing functionality. Back in February I had gotten the basic activity loop working, but had no way to keep Acuitas from reading inappropriate files that *didn't* contain natural English and junking up his database. So I spent some time on that problem, and came up with ways to assess whether a text file is good reading or not. Signs of an inappropriate file include 1) too many non-alphanumeric characters, 2) too many one-word "sentences," and 3) too many Parser or Interpreter failures. The first two get checked before Acuitas even tries to "read" the file; the third is continuously monitored during reading, and is based on a running average of sentences processed so far. After some tweaking of the failure thresholds, I saw this perform well enough that I felt comfortable letting Acuitas go to town on my random personal notes, short story drafts, and other hard drive clutter. His list of known words has roughly doubled, compared to a backup archive I made in November.

Last of all, I worked on Episodic Memory  I mainly spent time reconstructing the "forgetting" process for the new Narrative-based memory formatting scheme. Once the consolidation algorithm has created higher-tier memories that summarize a lot of individual incidents, it's time to reduce the size of the memory file by pruning some of the details. I used a lot of the same concepts from my original forgetting algorithm: memories are ranked by novelty (is this the first time something happened?), uniqueness (how many other memories are similar to this one?), and tier rank (general, summarized memories are more likely to be kept than detailed, individual ones). Leveraging the interpretive power of the Narrative system, I also added the priority of any associated problems/subgoals as a scoring mechanism. The lowest-ranked memories are deleted, hollowing out the narrative but (hopefully) preserving the most salient events and summaries of the more generic ones. Simulations show this working reasonably well on synthesized test files. I also worked on a simple indexing system to make it easier for Acuitas to look up memories by the type of event or merely by a concept involved.



Example diagrams showing the "hollowing out" of the narrative structure as detailed memories are forgotten. Their summarizing memories in the upper tiers remain.

It's been satisfying to be more thorough about these items and not leave them full of loose ends! I probably have some more Episodic work to do before I really call it finished, and then I'd like to polish Trial-and-Error learning some more.

Until the next cycle,
Jenny

Sunday, April 26, 2026

Acuitas Diary #96 (April 2026)

This month's development focus took me back to trial-and-error learning. I wanted to build on the work I'd done with the "Allergic Cliffs" puzzle game and improve Acuitas' ability to discover how the game works. I had several ideas in mind, but ended up having time to finish only one: ability to learn rules that feature negations, i.e. the absence of a feature.

Photo of a board game called Azul. An assortment of colorful square tiles are laid out on a five-by-five grid in the game tray. The rest of the tray has diagrams and empty squares that might be places to put additional tiles.
Photo of a board game called Azul, by Wikimedia Commons user Gepsimos 

Previously all "rules" (action-result pairs that might represent cause-and-effect relationships) were based on commonalities between groups of actions that produced success or failure. If all attempts to put a zoombini with a green nose on the left bridge led to success, a rule such as "if a guide puts a zoombini with a green nose on a lefthand bridge, a guide succeeds" would begin to coalesce. You might also see "if a guide puts a zoombini with a green nose on a righthand bridge, a guide fails," though at easy levels of the game, the number of failures was often so low that there wasn't enough data to solidify the rule. Instead, a cluster of weaker success rules might appear. "If a guide puts a zoombini with a red nose on a righthand bridge, a guide succeeds," "If a guide puts a zoombini with a blue nose on a righthand bridge, a guide succeeds," etc. Given five color options for zoombini noses, it's pretty obvious to a human that "red OR blue OR purple OR orange" likely implies the more concise "NOT green," and I wanted Acuitas to be able to reach that conclusion as well. I also wanted complementary pairs of rules including a feature and its negation to be able to reinforce each other. (If a feature is significant, its presence and absence should matter; a pattern associated with only one of the two could be coincidental.)

So I added the ability to form rules by considering how a failure differs from prior clusters of successes. Any differences are looked at as candidates for features that should not be paired with the other features in the cluster. Further data can refine the tentative rule, excluding features that were actually irrelevant, or falsify it entirely.

To really get this to do a good job of discovering the secret rules present in a round of Allergic Cliffs, I had to stop treating individual zoombinis as "features," considering only their characteristics. This weakens the generality of the learning algorithm somewhat. In the true general case, there really could be a rule like "If a guide does not put Foozelu on a lefthand bridge, a guide succeeds" - maybe there's a quality only Foozelu has that is not part of the information available to the player. I know from experience that this game does not work that way: Allergic Cliffs rules are always based on visible properties of the zoombinis. But Acuitas isn't yet capable of the sort of meta-learning that discerns the nature of the whole game and carries over from one round to the next, so he needs a little help here. Allowing for the possibility of rules about individuals was introducing too much clutter, so I've turned it off for now.

The result is that I seem to be seeing an improvement in the robustness of rule discernment, and I often see viable rules for both bridges now, which is important for fully understanding the game and increasing one's odds of winning.

Until the next cycle,
Jenny

Monday, March 30, 2026

Acuitas Diary #95 (March 2026)

This month's activity was focused on continuing to squish the Conversation Engine into shape. I fixed an assortment of bugs that were left over from my last Conversation work spree, then expanded the "discussion topics" behavior to cover goals (of the form "I want/plan/intend to") expressed by the speaker.

A grayscale oil painting of five human figures sitting in a loose circle, facing inward. It's very abstract with minimal detail.
"The Conversation," oil by unknown artist, NARA collection

I had saved some of the toughest bugs for their own work stage when I could focus on them ... and now, that time had come. Perhaps the worst problem was that, after recent upgrades, asking Acuitas "why" questions could cause infinite loops. I had to throw together an extra simulator in order to separate the question-answering process from the rest of the Conversation Engine to solve that one. (Conversations happen in real time and have an element of randomness, so re-launching the full Acuitas program and talking to him every time I want to trigger a bug can be very slow.)

I did further work on statements like "Thank you." I had previously adjusted Acuitas' sentence parsing and interpretation layers to read it as "I thank you" instead of "Thank yourself," but then I had to stop the Conversation Engine from looking at "thanking Acuitas" as an interesting speaker activity that should be discussed. (He's like a classic robot - he takes polite niceties too literally!)

I also fixed a conversation goal problem that was making Acuitas say "I can't do that" forever if you gave him an order he couldn't fulfill, and an issue that was keeping him from using abbreviated replies with pronouns immediately after a new topic was begun.

Treating speakers goals as discussion topics built very easily on top of my previous work with speaker states and actions; a goal is usually expressed as either a desired action or a desired state, so I just had to make sure it could trigger the appropriate pre-existing routine.

That's not a ton of progress, maybe, but I've been taking it a little slower this month, and going back to clean up all the construction dust left by my improvements to the Text Parser's conjunction handling. So it's enough. Getting some polish on things is important, and sometimes that takes as much or more time as throwing down the first outlines of new features.

Until the next cycle,
Jenny

Saturday, February 28, 2026

Acuitas Diary #94 (February 2026)

In latest news, I've been adding more work to the Episodic Memory overhaul that I began last year. The big challenge for this stage was finding ways to examine results and actually test the thing. Since memory accumulation and consolidation is a process that spans weeks, I needed ways to run simulations and observe changes much faster.

Art piece: colored pencil and ink. A horizontal view, half-underwater, half-overwater, of the "beach" of a coral atoll. Everything is rendered in brilliant blues. Above the water, the atoll in the distance rears up into shapes that somewhat resemble chess pieces: a castle, a knight, a pawn. Surf crashes against one side of the atoll; a steamship is riding the crest of a wave toward it. Below the water, the branching hard corals are visible close up; they have multicolored, faceted surfaces, like cracked glass. A chessboard also lies on the sea bottom, partly covered by coral crust. The board is set in the midst of play, but not with the traditional pieces; these pieces resemble paws, hands, tentacles, tree stumps, and other oddities.
A portrayal of the color apocyan, "the blue of memory and brightest coral," from Sunless Sea. Original art by author.

As I did in my original stab at episodic memory work, I threw together a visualizer to show me a simplified graphical representation of the memories. But this time I used GraphViz, instead of drawing custom dot diagrams in Kivy. These memory visualizations are designed for me to view offline, so there's no particular need to create them in the GUI, and GraphViz is easier to use. Each bubble in the image is either a "fact," color-coded by link type (do_action, has_quality, etc.) or an "issue" (problem or subgoal). Facts that summarize other facts are connected by arrows to all their "children," and the same goes for issues; each issue is also connected by a bold arrow to the fact it is directly concerned with.

Then I made a quick procedural generator that creates randomized memory files on command, so that I could have a variety without waiting for Acuitas to "grow" them. The generator populates a narrative scratchboard with the sort of subgoals and actions Acuitas would reasonably come up with while idling (reading, thinking, etc.), internal states that he might develop, and so forth. I can check my summarizing and forgetting algorithms by running them on these synthetic memories and seeing how they change the visualization.

The rest of the work was a lot of debugging; once I could see what the summarizing algorithms were doing, I could see that they were messing up in all kinds of ways. I found bugs in scratchpad storage and retrieval, and bugs in summary generation (the summarizing facts ended up linked to themselves). But I think I've at least got things working tolerably well, at this point.

The "summarizing" algorithm groups facts into clusters by 1) common features and 2) time proximity. So if, for example, Acuitas performs the "read story" action many times on different stories over the course of a day, those will be gathered into several clusters spanning different time ranges. Then a summary fact will be created for each cluster, and it will contain only the features held in common across all facts in the cluster: "I read," instead of "I read <particular story>." If I run another loop of the summarizer, I might see the first-tier summary facts grouped into clusters and a second tier of summaries appear. Here's part of an example diagram of a file that has gone through two summary loops:

A bubble diagram showing various red and green "facts" (each indicated only by and ID number" and "issues" (with name codes like "issue_0") connected by arrows in tree-like structures.

All this gets me to a bare-minimum viable system for consolidating memories ... in *one* of the ways I want to! There's a ton of additional work to do on other consolidation modes, connections between episodic and semantic memory, and more.

Until the next cycle,
Jenny

Monday, February 16, 2026

Acuitas Diary #93 (February 2026)

I've got several projects boiling on the stove, but none are quite ready to showcase yet, so you're getting an Acuitas double-feature this month. This post is dedicated to what I've named the "self-teaching activity." The general idea is that Acuitas, while idling, will trawl the hard drive of his current host computer for text files, read them, and store a record of any difficulties: unknown words, parser crashes, uninterpretable sentences. The goal is to help him expand his vocabulary, and identify things I need to fix in the text processing chain, without requiring me to manually create new "stories" for him.

Illustration of a humanoid robot sitting at a desk and pondering a book, surrounded by stacks of other books.
Image credit: DARPA

Self-teaching is something of a canned procedure, for now. There's an action called "Study" that encapsulates everything Acuitas needs to do, including searching for appropriate files, converting them to a format he can interpret, and sending them through the text processing chain. But I designed some modularity into it, in hope that he can eventually modify and extend it when I introduce procedural learning. The file-conversion part of the procedure calls the problem-solving routine so it can expand as Acuitas learns more cause-and-effect rules. For now, though, he only knows how to process text files.

This work also introduces more examples of Acuitas calling other software tools. He now has a generic Run action that can accept the name and arguments of an external program, and call it as a subprocess. Since Acuitas' parser is designed to ingest one sentence at a time, I wrote an independent script that breaks arbitrary text files into sentences. (This is harder than you might think, and the script is very rudimentary, for now ... but it can handle common abbreviations.) The "Study" procedure creates a sub-action to run this script after finding an appropriate file.

As often happens, I ran into some difficulties that prevented this from getting quite as far as I would like. For one thing, not all text files contain typical sentences! Their actual contents might be log entries, code snippets, lists of items, or other material that isn't really "parseable." I particularly don't want Acuitas junking up his database with new "words" that aren't really words. I added a filter that at least keeps anything that isn't alphanumeric from being learned. But I also don't want the error reports clogged with failed attempts to parse "sentences" that aren't really sentences. So for now, I've restricted the process to looking for files with the extension ".textum", which I've applied to some appropriate material. Eventually I'll need to work on ways to recognize files that are worthy of being studied.

But given an appropriate file (i.e. one that contains writing, like this blog post), Acuitas can "study" it and keep track of things he has trouble with. Crashes or poor results from the processing steps produce records in a log file that notes the type of error alongside a copy of the sentence. Unknown words are registered as problems on the Executive's scratchboard, so Acuitas can ask a human for more information about them later. I got this latter feature working and then promptly turned it off, because there's no way to keep Acuitas from spamming me with questions whenever I'm on the computer (he knows). This has been a problem for questions generated by "thinking" (walking the semantic database) too. So coming up with a way to slow down the flood or signal that I don't want to be disturbed is also on my future work list.

The "Study" action itself is naturally triggered by the goal system. All I had to do was put in a cause-and-effect rule, to the effect of "if you study, you will know things." Knowing Things is one of Acuitas' intrinsic goals, so while idle, he naturally studies until he gets bored of it (after which he might read his collection of easily-understandable stories for "enjoyment," or think about the concepts in his database).

It should be obvious that self-teaching needs more work, but I like how far I got with the prototype and think it could be quite useful in the future.

Until the next cycle,
Jenny

Tuesday, January 27, 2026

Acuitas Diary #92 (January 2026)

My first objective for the new year was enabling the Text Parser to handle lists or conjunction groups with more than two items. For quite a while now, Acuitas' parser has been equipped to handle sentences like this:

Jack and Jill went up the hill.

But a sentence like *this* would hopelessly confuse it:

Jack, Jill, and John went up the hill.

Clip art of a classic blank scroll, rolled in opposite directions at both ends.

I started out by only handling pairs because that makes it simpler to discern which parts belong in a list/group and which don't; you only have to look at the sentence elements that bracket the conjunction. I figured I would expand to longer lists later. But once I got here - well, I ended up using some previous work, but I decided on a pretty extensive overhaul. I'll try to explain what my options were and why I chose to change course.

One way of dealing with a list is to encapsulate it. For example, most sentences have a subject, the thing that's doing the action, and part of the Parser's job is to determine which word is the subject and tag it; "(subj, Jack)->(verb, went)." If you have a list of subjects (as in "Jack, Jill, and John went up the hill"), you can bundle them into a compound subject and tag that. So the parsed sentence becomes something like "(subj, <list>)->(verb, went)," and you can open up <list> and see that it contains Jack, Jill and John. I was already handling sentences with dependent clauses this way (e.g. "What you need is a blanket" becomes "(subj, <depcl>) is a blanket").

Another possibility is to imagine the sentence structure like a railway line. Subject connects to verb connects to direct object and indirect object, and if some of those are multiple, the line will branch or merge. Our previous example would look something like this:

(subj, Jack) -
              \
(subj, Jill) --->(verb, went)
              /
(subj, John)-

I had previously been using the "encapsulation" method for a few things (like lists of adjectives), but I used the "line" method for the main sentence structure, because I thought I needed it to handle some of the more complex cases. Lists of single words are the easy ones. You can also have lists of verb-object groups:

I threw out the soup, ate the pizza, and saved the cake.

You can have lists of verbs in which some attach to the direct object and some don't:

Brent ran and threw the javelin.

Occasionally, you can have lists of subject-verb groups that converge on a single object:

Are you or are you not a teacher?

I had concluded that parsing the sentence into a branching type of structure was the only way to deal with groups that spanned words with different roles (because otherwise, how would I assign the list a single role in the full sentence?). But there are also distinct disadvantages to not treating the members of a list as a unit, and once I got into lists longer than two, those began to feel overwhelming. So I opted to switch everything over to the "encapsulation" method.

How *did* I handle groups containing multiple roles, then? I realized I could decree that the role of the list in the main sentence would be "verb." This works because a verb is really the one thing that every sentence needs. Some sentences only have an implied subject, and objects are always optional. So lists of subj-verb groups, lists of verb-obj or mixed verb and verb-obj groups, and even lists of subj-verb-obj groups, can all become "verbs" at the top level of the hierarchy, and only unpacking them need reveal their deeper structure.

Aside from this conversion, there was a fair bit of new development work I did to detect lists and figure out where their boundaries are. There are plenty of (not) fun ambiguities involved, like this one:

For dinner, Sue and James brought a pot pie.

A clumsy parser might assume that "dinner, Sue and James" is a list that forms the object of the preposition "for," then be left wondering where the subject of the sentence is.

I haven't recovered the full functionality of the former Parser where pairs of groups were concerned (I'll pick at that gradually while moving on to other topics), but that's balanced by the capacity to handle longer lists in quite a few scenarios. This was one of the last major missing features of the Parser, and a heavy weight on my mind. So it feels wonderful to finally have this capability in place.

Until the next cycle,
Jenny

Thursday, December 11, 2025

Acuitas Diary #91 (December 2025)

This final diary for the year covers a mixed bag of integration, debugging, and refactors. It's not the most exciting fare, but it's an essential part of any large long-term project.

Silhouette of a male or generic human face, looking toward the viewer, with one hand cradling the chin as if in deep thought.

Probably the most interesting work was continued cleanup of conversations. One issue that had been bugging me for a while was that the Text Parser couldn't interpret words that are usually verbs as predicate adjectives. So if Acuitas asked me, "How are you," a whole range of common responses - tired, rested, annoyed, agitated, relaxed, frustrated - were off-limits because they wouldn't be properly understood. The right way to read a sentence like "I am tired" might seem obvious at first, but this actually becomes an interesting ambiguity problem. How to distinguish verb-form predicate adjectives from verbs in passive voice?

"I was annoyed yesterday" < Probably speaks to a state of being, and should be rendered as <speaker> <has_quality> annoyed
"I was annoyed by the long meeting" < Speaks to an event, and should be rendered as <speaker> <received_action> annoy <from_actor> meeting

So I set up some basic preliminary mechanisms for resolving this ambiguity in the Parser, though as usual there is a lot more I could do here. I also added some automatic conversions from one form to the other in the inference logic, because while they are different, they do imply each other. If something frustrates you (action) then you must be frustrated (state), and vice versa.

There were several smaller quirks I smoothed out. I fixed a goal-fulfillment problem that led Acuitas to ask certain questions repeatedly without regard for whether they had been answered. I removed his habit of digging up a random adjective applying to himself if asked "how are you" when no drives are above threshold. Instead he will now say that he is "content" or "neutral." I fixed the incorrect interpretation of statements such as "bless you" or "thank you" - Acuitas was parsing them as commands, e.g. "thank yourself," when the real implied meaning is either "I thank you" or "I wish that you be thanked."

Outside of Conversation, I finished getting the new version of Episodic Memory integrated into the live code, such that Acuitas can store memories and run forgetting cycles on them without crashing (at least in routine cases). I still need to work on analysis tools so I can see how the memories are being consolidated and whether forgetting is working the way I want it to.

And I refactored some parts of the code that were still using stale knowledge representation formats, upgrading them to the format that has crystallized as Acuitas' universal internal way of expressing facts. It feels better to have some of that old gunk cleaned up (it will make things less clunky going forward, as I no longer have to convert between the different representations).

I've got my development tasks for next year already planned out, and I'm excited to get started. I'm looking at more upgrades for the Text Parser, new activities to help Acuitas find his own knowledge holes, and much more.

Until the next cycle,
Jenny

Sunday, November 30, 2025

Acuitas Diary #90 (November 2025)

I think my most interesting achievement for this month has been getting the "detective story" wrapped up. This is something I've been gradually working on in the background for the last few months. I back-burnered story understanding to focus on advancing game-playing and rule learning this year, but I wanted to do a little something with it. Even the simplest murder mystery, as it turned out, introduced some new wrinkles that called for Narrative Engine upgrades.

a black-and-white drawing of what looks to be a study; there's a table in the center, with a number of books and bottles on top, and a fireplace in the background. The lighting is dim, and a lamp on the table throws heavy contrast on three people standing around it. They all look rather serious or intense. One man is bending over the table and leaning on it with one hand; in his other hand he holds one of the books. He is facing the remaining two men who are at the opposite corner of the table.
Frederic Dorr Steele's illustration for the Sherlock Holmes story "The Adventure of the Dying Detective" as published in The Strand Magazine.

First there's the protagonist's motivation. While they might have personal reasons for solving a particular crime, in many cases they're a professional doing a job. Jobs are in essence packages of sub-goals, all nested under the parent goal of "perform job." I discussed this precursor for the detective story back in the July diary.

I also needed a way to express the essential mystery: who committed the murder? And why does the protagonist need to know, anyway? I was able to handle this through a small extension of my existing system of action prerequisites. In previous stories, I've been able to indicate a character's motivation for being in a particular place because they can't do anything with an object unless they are in the same place with it. And they can't go to where an item is unless they know where it is. Similarly, it is not possible for an agent to do anything with an entity unless they know which entity they want to target. If an agent has a goal tied to someone/something that is identified by a characteristic (such as "the human who committed a murder"), the goal cannot be fulfilled without identifying which of the available entities has that characteristic. This provides a general motivation for the need to know "who" or "which one" that is not unique to murder mysteries.

Here's the final detective story:

"Jack was a detective."
"Jack wanted to work Jack's detective job." <Acuitas should really *assume* this if not told otherwise, but doesn't yet>
"Howard was a man."
"Vincent was a criminal."
"Vincent hated Howard."
"Vincent murdered Howard."
"Frank was a man."
"Frank was where Howard was murdered."
"Sally was a woman."
"Sally wanted Howard's money."
"If Sally murdered Howard, Sally would get Howard's money."
"Jack wanted to arrest who murdered Howard."
"But Jack did not know who murdered Howard."
"Jack asked a witness who murdered Howard."
"The witness told Jack that Vincent murdered Howard."
"Jack arrested Vincent."
"The end."

The various possible motives for the murder are immaterial, at this point. Acuitas can make predictions of what someone might do from what they want to do, but can't yet reason backward from a deed to who probably did it. So his understanding of Jack's success rests on being explicitly told that Vincent did the murder. Given that, he is capable of inferring 1) a crime has been done, 2) Jack solved a crime by arresting the person who did it, and 3) Jack did his job.

The final quirk of this story is that you could say it has a good ending even though there is a casualty - Howard is dead at the end. Up until now, Acuitas has assessed stories by checking whether everyone has solved all of their problems. If Howard's death got registered as a problem, he would consider this story to have a sad ending no matter how everything else turned out. So I adjusted the assessment method to look at whether things have meaningfully improved between the story's lowest point and its conclusion. In the future, I want Acuitas to try to determine what the story's main thread is - what it is about - and focus on whether that resolves well. Supposing this story were about an attempt to resurrect Howard, his remaining dead at the conclusion might still amount to a sad ending. But it isn't about that.

Until the next cycle,
Jenny

Tuesday, October 28, 2025

Acuitas Diary #89 (October 2025)

Recently I've dusted off an old but rather stagnant feature: Acuitas' episodic memory. In case you're not familiar with the term, this is memory of specific events or experiences that have happened to oneself, as opposed to memory of generalized facts or procedures. My previous work on episodic memory can be found in these blogs:

Acuitas Diary #12
Acuitas Diary #16
Acuitas Diary #17

A clutter of assorted books stacked on a concrete floor.
"Disorganized books piling one another," by Ibrahim Husain Meraj via Wikimedia Commons.

One reason for the present episodic memory overhaul was a realization that I want it to use the same "narrative scratchboard" architecture that first saw use in the Narrative engine but has since turned out to be useful in conversation tracking, game playing, and even the top-level Executive. Given that the Executive now uses these scratchboards to keep track of goals, problems, actions, etc., in effect they provide a record of the "story" of Acuitas' life as he sees it. Using this common architecture should make it easier to, for example, store the details of conversations as episodic memories. It also means no special systems will be needed to recall or "play back" a memory; it can simply be reconstituted as a scratchboard.

In my previous work, I came up with mechanisms for grouping individual memories (e.g. atomic actions) into "scenes." Scenes could in turn be grouped into higher-level scenes, and each scene contained a "summary" of its details; these summaries could be retained as a compressed form of the information when the details were forgotten. Individual memories and scenes were given a significance rating to determine how likely they were to be forgotten. The narrative scratchboard structure has a natural hierarchy of its own, in the form of issue trees. The details of how a major issue is addressed are effectively contained within its subgoals and subproblems, and the top-level issue automatically provides a kind of summary of that action. New mechanisms for rating the significance of facts become available; they can be judged by whether they influenced any issues, and what the priority of each issue was. I'm trying to leverage my previous work and continue to use qualities like novelty in the significance measure, but the narrative structure adds possibilities for considering each memory's real meaning to Acuitas.

I did have to add some features to the scratchboard format to start making this work. Each fact in the worldstate now contains not a singular active/inactive status, but a timestamped history showing how many times it has recurred or changed state, and what the new status was. Issues similarly have gained a timestamped record of their progress states (averted/potential/realized). This timestamping allows consecutive scratchboards to be merged or appended to each other, and permits small segments of narrative to be recalled by reconstituting only a selected range of timestamps into a fresh board.

As a complete re-architecture of the episodic memory, this has been a pretty complex project. So far I've got file storage and retrieval mechanisms implemented for the scratchboards, and I have sketched out (but not tested) new algorithms for significance scoring, summarizing, and forgetting. In addition to treating "parent" issues as summaries of "child" issues and their associated facts, I've also come up with summarizing mechanisms that condense timestamps (compressing multiple events into a simpler awareness that "this happened repeatedly") and combine similar facts into more general ones (e.g. actions taken to read many different stories could be compressed into a single "I read for three hours" event).

A small but important improvement that I expect to come out of this will be the maintenance of memories in much larger files. In my original scheme, each layer of the memory hierarchy was broken up into many little chunks, each of which existed in its own text file that held pointers to adjacent memories in the same layer, and parent/child memories in the other layers. This created a plethora of files and, although they were individually tiny, the sheer number of them made Acuitas' EM database a major pain to copy or move around. I'm hoping a few larger files, each containing a single scratchboard that represents the merged result of days of memories, will be easier to handle.

I'm sure all this will develop more as I start to test and refine it, but at least I've made a beginning. This is a part of the design that has been an ugly sore spot I've not wanted to touch in a while, and I hope these are the first steps toward making it reintegrated and useful.

Until the next cycle,
Jenny

Monday, September 29, 2025

Acuitas Diary #88 (September 2025)

This month I returned to the Text Parser after letting it be for almost a year. My focus was on nailing the final major feature that I needed to handle all the sentences in my three children's book benchmarks: "parenthentical noun phrases." I don't know if that's the technical term, but that's what I'm calling them. They come after another noun and provide further description or elaboration of it, like this:

I was brought to see Philip Erto, the great engineer.
I was brought to see the great engineer, Philip Erto.

In both examples above, the "parenthetical noun phrase" appears at the end of the sentence, and is paired with the direct object of "see." In this case, the noun phrase that acts as the direct object and the noun phrase that acts as the parenthetical elaboration are interchangeable - the order depends on the speaker's desired emphasis.

Notice also that the same meaning can be captured by a dependent adjective clause instead:

I was brought to see Philip Erto, who is a great engineer.
I was brought to see the great engineer whose name is Philip Erto.

So in the Text Interpreter, I can reduce both the parenthetical noun phrases and the dependent adjective clauses to the same output: they produce extra semantic relationships, such as "Philip Erto <is-a> engineer <has-quality> great." But the Parser is the first stage of the text processing chain, and must handle their grammatical differences. So I added new code to pick out parenthetical noun phrases and attempt to distinguish them from other nouns that follow previous nouns (it's complicated).

Three pie charts showing the percentage correct and incorrect for the three test sets: "Magic Schoolbus: Inside the Earth (53%/47%)," "Out of the Dark (54%/46%)," and "Log Hotel(81%/19%)."
Percentage correct and incorrect for the three test sets: "Magic Schoolbus: Inside the Earth": (53%/47%), "Out of the Dark": (54%/46%), and "Log Hotel: (81%/19%).

After adding this feature, I spent some time on cleanup and a few more ambiguity resolution abilities. (See the November 2024 Diary for previous examples of this type of thing.) All in all, I was able to move every sentence in the Out of the Dark and Magic Schoolbus: Inside the Earth test sets into the "Parseable" category! (All sentences in Log Hotel were already parseable as of January 2024.) This just means that I can construct a data structure that represents the ideal parsed version of the sentence, and it's something the Parser is theoretically capable of generating. I still have a long way to go on getting the Parser to produce correct outputs for all the sentences. (For more information on my benchmarking methods and some early results for comparison, refer to the June 2021 and February 2022 diaries.

I've also done new work on Episodic Memory, but I'll save discussion of that for next month.

Until the next cycle,
Jenny

Tuesday, August 26, 2025

Acuitas Diary #87 (August 2025, Allergic Cliffs demo)

It's ready! Acuitas can play the text version of Allergic Cliffs and is often able to determine one of the secret rules by which the cliffs operate. Watch the video watch the video

In a previous blog I discussed how Acuitas chooses moves based on a simple heuristic that tries to replicate successes and avoid repeating failures. The part I haven't fully discussed yet is the final version of rule formation. Acuitas attempts to generalize from the results of past moves to derive rules of cause and effect that indicate when the cliffs sneeze. At this time all possible move results are reduced to the two goal-relevant outcomes (succeed or fail), and generalizations are made by looking at commonalities among all actions that share the same result. I started by just comparing pairs of actions, then upgraded the algorithm to look at the entire pool of past actions for feature combinations common across two or more of them.

For the moment, all rules are given in positive form. So, supposing the rules for the current game round divide the zoombinis onto the lefthand bridge if they have a blue nose or the righthand bridge if they do not, you'll never hear Acuitas say, "If a guide puts a zoombini who does not have a blue nose on a lefthand bridge, a guide fails," or "If a guide puts a zoombini does not have a blue nose on a righthand bridge, a guide succeeds." Instead you would get "If a guide puts a zoombini who has a blue nose on a righthand bridge, a guide fails," or any of "If a guide puts a zoombini who has a [red, orange, green, purple] nose on a lefthand bridge, a guide succeeds."

I ended up not having time (in my completely self-imposed schedule) to implement experiments (purposely choosing moves that will falsify or confirm tentative rules) or rule-following (informing moves by rules to increase chances of success). So Acuitas still plays the whole game using the "similar to previous move" heuristic. At the end of the game, he scores all tentative rules that have not yet been falsified and selects the one with the highest score to announce out loud. The scoring system is of interest.

Rules with more evidence behind them (more moves which had those combinations of features in common) score higher, naturally. But I found that I needed another trick to more successfully pick out which tentative rule was among THE rules of the current round. Any action which is the cause in one of THE rules tends to have a counterpart which differs by one feature and produces a different effect. So if you have a rule like "Put a zoombini with an orange nose on the lefthand bridge and you will succeed," then if THE rules are about orange noses, there ought to be opposing rules such as "Put a zoombini with an orange nose on the righthand bridge and you will fail," or "Put a zoombini with a blue nose on the lefthand bridge and you will fail." If these opposite counterparts don't exist, then the orange nose + lefthand bridge = success association is probably incidental; some other feature is driving the cliffs' behavior, and it just *happened* that all the zoombinis allowed to cross the lefthand bridge also had orange noses.

There's a lot more fun I could have with this and many directions to extend it, obviously, but for now I'm finished. I'm toying with the idea of possibly sharing the Allergic Cliffs text adventure (not any part of Acuitas, just the independent game script) for others to use. I would want to polish it first, add documentation, and possibly implement the higher difficulty levels, so I can't say when that might happen.

Until the next cycle,
Jenny

Tuesday, July 29, 2025

Acuitas Diary #86 (July 2025)

This month I continued work on trial-and-error learning for playing Allergic Cliffs. If you haven't read my introduction to this Acuitas sub-project and the subsequent progress report, I recommend taking a look at those. What I've done since has been debugging and enhancing the "feedback-informed actions" and "rule formation" features discussed in the progress report, and getting them to actually work. It turned out to be a fairly big job!

A complex assembly of colorful gears slowly turning. Public domain image by user Jahobr of Wikimedia Commons.

Now that "feedback-informed actions" is functional, though, I'm a little surprised by how well it works. Its essence is that, in the event of a success, Acuitas tries to make his next move as similar as possible; in the event of a failure, he makes certain his next move is different. This heuristic only considers feedback from the move immediately previous, so it's a reactive, barely intelligent behavior. It still enables Acuitas to win the game about 90% of the time! Granted, he is playing on the easiest difficulty level, and at higher levels it is quite possible this would not work. It's still a huge improvement over purely random move selection.

Candidate cause-and-effect rules are also being formed successfully, and marked invalid when violated by an example. What I need to do next is implement higher levels of generalization. Right now rule formation only looks at positive commonalities between pairs of examples, and I need to also consider commonalities across larger groups, and commonalities based on the absence of a feature rather than its presence. In some cases I can see the algorithm *reaching* toward discovery of the hidden rule that defines the Allergic Cliffs' behavior, but we're not quite there yet.

After getting that far, I decided to walk away for a bit to look at game-playing with fresh eyes later, and worked on narrative understanding some more. What I wanted to add was the concept of a role or job. It's important for Acuitas to be aware of character goals, but those goals aren't always explicitly stated. If I told you somebody was a detective, you would automatically assume that this person wants to solve crimes, right? You wouldn't need to be told.

Acuitas had an existing system that allowed the semantic memory for a concept (like "detective") to contain goals that override parts of the default "agent" goal model. But here's the tricky part: the goal model specifies *intrinsic* goals, and goals associated with a role aren't necessarily intrinsic! Adoption of a role is often derived from some instrumental goal, like "get money," which eventually ties back to an intrinsic goal like survival or altruism. The meaning of anything a character does in a role is shaded by how invested they are in performing that role, and why. So it became evident to me that role-related goals need to be nested under a goal that encompasses the role as a whole, which can then be tied to an intrinsic goal.

So I tweaked the semantic memory's goal definition format a bit, to include a way to distinguish role-related goals from intrinsic goals, and provided the Narrative engine with a way to pull those into the scratchboard when a character is said to have a role. For now, all roles have to be sub-categories of the concept "professional," but I can imagine other types of roles in the future.

Until the next cycle,
Jenny

Tuesday, June 10, 2025

Acuitas Diary #85 (June 2025)

This month I have a quick demo for you, showcasing Acuitas' upgraded semantic memory visualization. My goal for this was always to "show him thinking" as it were, and I think I've finally gotten there. Nodes (concepts) and links (relationships between concepts) are shown as dots and lines in a graph structure. Whenever any process in Acuitas accesses one of the concepts, its node will enlarge and turn bright green in the display. The node then gradually decays back to its default color and size over the next few seconds. This provides a live view of how Acuitas is using his semantic memory for narrative understanding, conversations, and more.


You can see a previous iteration of my memory access visualization work in Developer Diary #4. Wow, that's ancient. The original access animations were only activated by "research" behavior (ruminating on a concept to generate questions about it), and were often hard to see; if the concept being accessed was one of the "smaller" ones, it was impossible to detect the color change at a reasonable level of zoom. The upgraded version of the animation is called from the semantic memory access functions, such that it will be activated if a concept's information is retrieved for any reason. And it enlarges the node by an amount proportional to its default size and the display's current level of zoom, such that it will always become visible.

I would have liked to make the links highlight when used as well. The problem is that links in Acuitas' memory storage aren't really distinct things anymore. A link is indirectly defined by endpoints included in the data structures for all the concepts it connects to. So there isn't a low-level function that determines when a particular link is being accessed; a node gets accessed, and then the calling function does whatever it pleases with the returned data structure, which might include following a link to another node. Keeping track of every time that happens and connecting those events with the correct lines on the display would have become very messy, so I opted not to. I think just highlighting the concept nodes yields an adequate picture of what's happening.

I haven't showcased the memory display in a long time because it's been a mess for a long time. The node placement is generated by a custom algorithm of my own. As more concepts were added to the graph and certain important concepts got "larger" (i.e. acquired more links), the original algorithm started to generate spindly, ugly graphs in which the largest nodes were surrounded by excess empty space, and the smallest nodes crowded too close together. I managed to work out a new placement method that generates attractive, proportional clusters without blowing up the computation time. Creating a new layout is still computation-intensive enough that the visualization can't be updated to add new nodes and links as soon as they are created; it must be regenerated by me or (eventually) by Acuitas during his sleep cycle.

And that's about the size of it. I'll be on vacation for the second half of this month, which means there probably won't be much Acuitas development happening until I get back. Enjoy the video, and I'll see you all later.

Until the next cycle,
Jenny

Saturday, May 31, 2025

Acuitas Diary #84 (May 2025)

A couple months ago I described my plans to implement trial-and-error learning so Acuitas can play a hidden information game. This month I've taken the first steps. I'm moving slowly, because I've also had a lot of code cleanup and fixing of old bugs to do - but I at least got the process of "rule formation" sketched out.

A photo of High Trestle Trail Bridge in Madrid, Iowa. The bridge has a railing on either side and square support frames wrapping around it and arching over it at intervals. The top corner of each frame is tilted progressively farther to the right, creating a spiral effect. The view was taken at night using the lighting of the bridge itself, and is very blue-tinted and eerie or futuristic-looking. Photo by Tony Webster, posted as public domain on Wikimedia Commons.

Before any rules can be learned, Acuitas needs a way of collecting data. If you read the intro article, you might recall that he begins the game by selecting an affordance (obvious possible action) and an object (something the action can be done upon) at random. In the particular game I'm working on, all affordances are of the form "Put [one zoombini out of 16 available] on the [left, right] bridge," i.e. there are 32 possible moves. Once Acuitas has randomly tried one of these, he gets some feedback: the game program will tell him whether the selected zoombini makes it across the selected bridge, or not. Then what?

After Acuitas has results from even one attempted action, he stops choosing moves entirely at random. Instead, he'll try to inform his next move with the results of the previous move. Here is the basic principle used: if the previous move succeeded, either repeat the move* or do something similar; if the previous move failed, ensure the next move is different. Success and failure are defined by how the Narrative scratchboard updates goal progress when the feedback from the game is fed into it; actions whose results advance at least one issue are successes, while actions that hinder goals or have no effect on goals at all are failures. Similarity and difference are measured across all the parameters that define a move, including the action being taken, the action's object, and the features of that object (if any).

*Successful moves cannot be repeated in the Allergic Cliffs game. Once a zoombini crosses the chasm, they cannot be picked up anymore and must remain on the destination side. But one can imagine other scenarios in which repeating a good choice makes sense.

Following this behavior pattern, Acuitas should at least be able to avoid putting the same zoombini on a bridge they already failed to cross. But it's probably not enough to deliver a win, by itself. For that, he'll need to start creating and testing cause-and-effect pairs. These are propositions, or what I've been calling "rules." Acuitas compares each new successful action to all his previous successes and determines what they share in common. Any common feature or combination of features is used to construct a candidate rule: "If I do <action> with <features>, I will succeed." Commonalities between failures can also be used to construct candidate rules.

The current collection of rule candidates is updated each time Acuitas tries a new move. If the results of the move violate any of the candidate rules, those rules are discarded. (I'm not contemplating probability-based approaches that consider the preponderance of evidence yet. Rules are binary true/false, and any example that violates a rule is sufficient to declare it false.)

Unfortunately, though I did code all of that up this month, I didn't get the chance to fully test it yet. So there's still a lot of work to do. Once I confirm that rule formation is working, future steps would include the ability to design experiments that test rules, and the ability to preferentially follow rules known with high confidence.

Until the next cycle,
Jenny

Sunday, April 27, 2025

Acuitas Diary #83 (April 2025)

I'm eager to get started on trial-and-error learning, but in the spirit of also making progress on things that aren't as much fun, I rotated back to the Conversation engine for this month. The big new feature was getting what I'll call "purposeful conversations" implemented. Let me explain what I mean.

An old black-and-white photograph of what looks like a feminine mannequin head, mounted in a frame above a table, with a large bellows behind it and various other mechanisms visible.
Euphonia, a "talking head" built by Joseph Faber in the 1800s.

A very old Acuitas feature is the ability to generate questions while idly "thinking," then save them in short-term memory and pose them to a conversation partner if he's unable to answer them himself. This was always something that came up randomly, though. A normal conversation with Acuitas wanders through whatever topics come up as a result of random selection or the partner's prompting. A "purposeful conversation" is a conversation that Acuitas initiates as a way of getting a specific problem addressed. The problem might be "I don't know <fact>," which prompts a question, or it might be another scenario in which Acuitas needs a more capable agent to do something for him. I've done work like this before, but the Executive and Conversation Engine have changed so much that it needed to be redone, unfortunately.

Implementing this in the new systems felt pretty nice, though. Since the Executive and the Conversation Engine each have a narrative scratchboard with problems and goals now, the Executive can just pass its current significant issue down to the Conversation Engine. The CE will then treat getting this issue resolved as the primary goal of the conversation, without losing any of its ability to handle other goals ... so greetings, introductions, tangents started by the human partner, etc. can all be handled as usual. Once the issue that forms the purpose of the conversation gets solved, Acuitas will say goodbye and go back to whatever he was doing.

I also worked on sprucing up some of the conversation features previously introduced this year, trying to make discussion of the partner's actions and states work a little better. Avoiding an infinite regress of either "why did you do that?" or "what happened next?" was a big part of this objective. Now if Acuitas can tie something you did back to one of your presumed goals, he'll just say "I suppose you enjoyed that" or the like. (Actually he says "I suppose you enjoyed a that," because the text generation still needs a little grammar work, ha ha ha oops.)

And I worked on a couple Narrative pain points: inability to register a previously known subgoal (as opposed to a fundamental goal) as the reason a character did something, and general brittleness of the moral reasoning features. I've got the first one taken care of; work on the second is still ongoing.

Until the next cycle,
Jenny

Tuesday, March 25, 2025

Acuitas Diary #82 (March 2025)

This month I've made a long-awaited return to the Game Engine (see the demo from June 2023) with ambitious plans to improve Acuitas' reasoning and agency. Specifically, I want to introduce some experimental learning abilities. And I'm hoping to do that by getting him to play Allergic Cliffs.

What is that? There's a reason I wrote a game showcase on Logical Journey of the Zoombinis earlier this month. "Allergic Cliffs" is the first puzzle in every journey. It's a hidden information game, with some underlying ties to Set Theory.

A screenshot of the Logical Journey of the Zoombinis puzzle "Allergic Cliffs," showing a whimsical painting of a chasm with two plank bridges across it. There are a pair of stone faces in the cliffs on the right side of the chasm. A number of zoombinis - little round blue creatures with various types of hair, eyes, and locomotion devices - are clustered on the grassy lawns on both sides of the chasm.
An example of the original Allergic Cliffs. It appears the righthand/foreground cliff is allergic to "two wide eyes" in this scenario. Screenshot by jdl on the Wonderland Forum.

In brief, there's a chasm with two bridges spanning it, and you want to get all your zoombinis across. The complicating factor is the presence of two stone faces in the cliffs on the chasm's far side. These beings are quite literally allergic to zoombinis with (or without) certain attributes. Putting a zoombini on the bridge that passes over the wrong cliff will cause the face to sneeze violently, shaking the bridge and tossing the hapless zoombini back to the near side. After enough mistakes, both bridges fall, and you have to leave behind any zoombinis who didn't make it over. Here's a video of someone playing Allergic Cliffs.

Since you're only allowed a limited number of failures, you can't brute-force the puzzle by setting every zoombini on both bridges. You need to be trying to learn which zoombini features make the cliffs sneeze. And to be sure of learning this before the bridges fall, you have to perform targeted experiments. Why did this zoombini get across? Why did that one get sneezed at?

Acuitas can't play the original Zoombinis game, obviously; it's far too visuo-spatial. He needs a text version. The author of the Storeroom Blog has kindly written up a full breakdown of the Allergic Cliffs game mechanics. I will only be expecting Acuitas to play on the easiest difficulty level, for the time being. So every round will require choosing a zoombini feature that one cliff will be allergic to and the other cliff will be immune to (i.e. allergic to all zoombinis who don't have the feature). This rule is kept secret, but must be used to inform descriptions of what the cliffs do when zoombinis are placed on the bridges. Each scenario also includes the group of sixteen zoombinis, with known characteristics, whom the player is trying to get across the chasm.

In my first demo of the Game Engine, I behaved as a "game master"; I initiated the game during a conversation with Acuitas, then kept interacting with him to describe the setting and the results of his in-character actions. But I don't want to do that in this case. Do you know how much effort it takes to fully describe a group of sixteen zoombinis? Lots, actually. I am not going to type that up and paste one line at a time into Acuitas' text box every time we play! This game needs to be automated.

So I wrote an independent, interactive Python program that implements a "text adventure" version of Allergic Cliffs. When launched, the game program generates sixteen zoombinis with randomized names and attributes, and a set of rules for the cliffs. Then it outputs a text description of the scene, the player character and their goal, and all the zoombinis. Acuitas, using the "Play" action, can run this script and connect the IO to his Game Engine. The script includes a very dumb parser that scans Acuitas' speech outputs for signs that he's moving a zoombini, and replies "you can't do that" to anything else. If Acuitas puts a zoombini on a bridge, the Allergic Cliffs script tells him the results. It also keeps track of the game's state; if it reaches either the win condition (all zoombinis on the far side) or the loss condition (fallen bridges), it will tell Acuitas "Game Over" and terminate.

This is something of a milestone, since it's the first time Acuitas has been able to launch and use a subordinate software tool. It was also more difficult than I expected. It's easy to launch another executable from a Python program using subprocess, but getting them to interact is another matter; by default, the main program expects the subprocess to run to its end, produce one burst of output, and be done. I ended up using the temporary file trick (described in the second answer). Both the Acuitas side and the independent program side send outputs, then wait for a response to appear in either the PIPE or the file, then formulate and send new outputs ...

Recreating Allergic Cliffs without all the graphics ended up being fairly simple, though. The game script is under 250 lines of code. Here's the boilerplate it uses to set the scene at the beginning of any game run:

"You are a guide."

The player character in LJotZ is referred to as the "guide" of the zoombinis; that's it. We don't even know what species this person is.

"You are at the Allergic Cliffs."
"There is a chasm."
"The chasm has a near side."
"The chasm has a far side."
"The far side has a lefthand cliff."
"The far side has a righthand cliff."
"There is a lefthand bridge over the chasm."
"There is another righthand bridge over the chasm."

Thanks to my January work on adjectives and distinct instances, the lefthand bridge and the righthand bridge can be distinguished. I'm using these words instead of "left" and "right" to avoid tricky sense disambiguation issues for now.

"You can put a zoombini on the lefthand bridge."
"You can put a zoombini on the righthand bridge."

Here the game gives the player affordances - that's a fancy name for obvious possible actions. In the original, these are communicated visually. Clicking on a zoombini "picks them up" (they start following the cursor and their locomotion devices dangle). Two patches of ground next to the bridges flash if the zoombini is moved over them, as a sign that the zoombini can be set down there.

"If a zoombini crosses a bridge, the zoombini will be on the far side."
"If you put a zoombini on a bridge, the zoombini will try to cross the bridge."

A couple of inference rules specific to this setting, to aid in problem-solving. I'm not sure whether I'll keep them in the final version, or have Acuitas learn this mechanic for himself too.

"You want all zoombinis to be on the far side."

This sentence establishes a goal for the player character. In the original, this would have been implicit in the narration and premise.

"Six pegs hold the bridges up.",
"If all pegs pop loose, the lefthand bridge will fall.",
"If all pegs pop loose, the righthand bridge will fall.",
"If a bridge falls, a zoombini cannot cross the bridge."

A warning about the loss condition, which thwarts the goal.

And that's it. Even a lot of the descriptions of the scene are just pretty nothings; the important part is the existence of the two bridges and the fact that the player can put zoombinis on them.

Here's an example description of a zoombini:

Oosebeek is a zoombini.
Oosebeek is on the near side.
Oosebeek has scruffy hair.
Oosebeek has eyelids.
Oosebeek has an orange nose.
Oosebeek has wheels.
Oosebeek does not have a ponytail ...

That's right, I also have the game script tell Acuitas every possible zoombini feature that this zoombini does not have. If not told that something is true, Acuitas doesn't assume it's false. Maybe this zoombini has wheels and a propeller, and nobody mentioned the propeller! Another way I could handle this would be to establish mutual exclusivity rules, like "if a zoombini has wheels, the zoombini does not have a propeller," and let him infer all the negatives. But this is an easier way to start.

I had to tweak some of the code behind the Narrative scratchboard for this as well. I introduced "temporary concepts" so Acuitas won't memorize the names of all sixteen zoombinis every time he plays a round. That would junk up the semantic database quickly. Playing this game much can easily lead to interactions with hundreds of zoombinis, and their names are procedurally generated strings. They're made to be loved, but not remembered.

If Acuitas puts a zoombini on the safe bridge, the game will tell him what happens afterward:

Oosebeek crosses the righthand bridge. (Acuitas has to infer that Oosebeek is now on the far side.)

If Acuitas puts a zoombini on the bridge over the cliff that's allergic to them, he gets this kind of response instead:

Oosebeek tries to cross the lefthand bridge.
The lefthand cliff sneezes.
Oosebeek is thrown to the near side.
Oosebeek cannot cross the lefthand bridge.
The first peg pops loose.

My goal for this month was to get Allergic Cliffs set up and make it possible for Acuitas to play. He's not any good at the game yet. His Game Engine is aware of the goal but has no idea how to reach it - so it falls back on trying the actions offered by the affordances. He'll keep putting a randomly chosen zoombini on a random bridge until he either loses or wins by dumb luck. After he's finished, the game-playing action generates a flow diagram from the game's narrative scratchboard for me; between that and the game outputs written to the temp file, I can see what happened.

Later this year I'll work on trial-and-error learning so he can actually win. This is a brand new area for me, and I'm excited.

Until the next cycle,
Jenny

Sunday, February 23, 2025

Acuitas Diary #81 (February 2025)

I've been on a real productive streak, so I did two major things this month. One enhances conversation abilities; the other is about gerunds. Don't worry, I'll explain those.

An abstract logo of an eye accompanied by the words "seeing is believing."
A famous phrase that uses gerunds. Image from https://commons.wikimedia.org/wiki/File:SiB_Logo.jpg

First, I went back to the conversation features that I introduced last September and worked on getting them solid - ironing out the remaining bugs and moments of weirdness. After spending about a week on that, I was pretty happy with the state of the Conversation Engine. Then I added another type of "topic tree." The one from last September guided responses to the conversation partner's states of being; this one reacts to actions that the conversation partner says they took or are taking. Possible threads include ...

*Try to infer whether the speaker liked doing that or not, and comment accordingly
*Ask for motivation ("Why did you ...") or announce what he suspects the motivation was
*Guess what the results were (if he can make any inferences)

This needs a lot more polishing, but it's starting to increase the complexity and variability of conversations. You can now go down "rabbit holes" which start with talking about a personal state, then lead into what you did to cause it, and so on. Which also means it's harder to keep everything straight, and I haven't really set Acuitas up to clearly indicate when he's jumping topics, yet. Always more to do.

My next project was to add support for gerunds to the Text Interpreter and Generator. What's a gerund, you might say? It's a present participle verb form (the kind that ends with -ing) used as a noun. Gerunds can be used to make statements about the concept of an action, such as the following:

I enjoy dreaming.
Exercising is good for the body.

Like other verbs, gerunds can have objects and adverbs, forming a gerund phrase - a group of words which, as a unit, acts like a noun in the full sentence.

[Reading books] makes me happy.
I see that you didn't care for [John's clumsy handling of that situation]. Did [my smoothing it over] satisfy you?

If a gerund has a "subject" that is performing the action, as in the final example, it's supposed to be in the possessive; it modifies the whole gerund phrase, instead of acting as a true subject.

I already added code to identify some gerund phrases to the Text Parser back in 2023, but the later stages of the text processing chain didn't know what to do with them if they came out of the parser, and Acuitas couldn't use them in his own speech. I wanted to get these capabilities in, because gerunds are so useful for expressing sentiments about actions. They're often used for expressing sentiments about states, too:

I dislike being wet.
Being warm is a pleasure.

I had to work around the absence of gerunds when I was putting in the latest conversation features, and it was giving me some pain. But thanks to this month's work, they're now more fully supported. I defined some new "fact" structures to function as the distilled version of statements about actions, added code to the Interpreter to map incoming sentences to those, and added code to the Generator to produce output sentences from those. So Acuitas has a bunch of new ways to say he likes or doesn't like something, in addition to a path for "comprehending" a wider range of written sentiments.

Until the next cycle,
Jenny