Keep link on agent's query update

(Bruno Moreira) #1

I have an agent pulling notes aliases from several parts of my document; inside that agent, in map view, I create some graphical links (dragging the little arrow). If I later update that agent’s query, all the aliases seem to be deleted / recreated again, thus deleting all the links I had manually placed. Is there any way to make the agent only update / add new notes and keep existing ones (and their links?). Thanks!

(Mark Anderson) #2

A few background points:

  • agents collect aliases of actual (‘original’) notes
  • aliases have their own links
  • dragging links to/from agent aliases links the alias not the original
  • updating the agent (especially if the query changes) may cause a new alias to be created.
  • aliases do not inherit/share the links of other aliases.
  • text links, i.e. links generated in the body of the note, are shared by original and any aliases of it.

These may seem like errors, insofar as they don’t suit your task but the status quo reflects the needs of other use cases. I think the short answer to your question is no, but you could add minimal text to you notes and use a word, or even a character, to make a text link from the alias to the other notes. Of course if the target is an alias then you may have a problem.

One other approach is to run the agent, select all the aliases and then drag them into a container note. They are now stand-alone aliases, as if you had made them manually. They will only get deleted if you do so manually. Meanwhile the agent will make new aliases to satisfy the query. So, if you drag the aliases to a ‘static’ map - i.e. outside the agent, then your links will persist. Not quite the solution you hoped for, but at least possible.

(Bruno Moreira) #3

Thanks for the clarification. Indeed, it’s a particular need I guess… my current workflow is trying to be like this:

  1. working in a deeply nested hierarchy of original notes – in outline view;
  2. using agents to selectively pull aliases of those notes’ aliases based on key attributes into a flat - non-hierarchical view;
  3. creating links and positioning (xPos, yPos) those aliases inside the agent, to make visual connections – in map view;

So I guess I am trying to get an agent to have both a dynamic nature (pulling new aliases or redefining its query) with a static nature (preserving the manually defined xPos, yPos and links of notes that have already been fetched by the agent).

All the background points you made make sense, I just wished a “tiny” change, in order for

“updating the agent (especially if the query changes) may cause a new alias to be created”

to become

“updating the agent (especially if the query changes) may cause a new alias to be created if an alias with the same name does not exist yet inside the agent”

(but this little change has deep repercussions on TBX logic I guess! :smiley: )

(Mark Anderson) #4

You’d have to ask the closing question of Eastgate. I think agent only make a new one if needed, i.e. not each cycle.

If you want the agent update and not move/re-arrange aliases on the map, see $CleanupAction as that may help. The other you can do is to turn the agent off via $AgentPriority or the Action Inspector’s query tab. One the agent is ‘off’ the query isn’t run so obviously the aliases are now unaffected and the agent map functions as if it were a map of manually created aliases in a normal container.

(Bruno Moreira) #5

My problem is that I may often reset the agent’s query … and that makes TBX delete and re-collect all aliases. I guess by design.

I’m trying a different approach … with this rule:


I can make a first alias inside the agent automatically create a link to a second alias inside that same agent whose $Name is defined in the $item keyattribute on the first alias.

$item is of set type. This works for one value, for eg., when $item = “1”; but I’d like it to work for all values of $item, for eg. when $item = “1;5;6” it should make connections to all aliases whose $Name is 1, 5 or 6 … but I can’t seem to make it work…

(Bruno Moreira) #6

Please ignore this last comment. I’m getting senile :slight_smile: already answered in the old forum at I just had forgot about it! Thanks.

Visualizing attribute-defined relations through links: from a nested outline of parts into a flattened map view of the whole
(Andreas Grimm) #7

I found this link to a suggestion @mwra made a year ago in the former eastgate-Forum where Mark also provided a demo-file. This works very well. Thanks.

There are 2 questions inspired by this thread here and the example Mark gave in the former eastgate-forum I’d like to put forward:

  1. How would it look like to take Mark’s example one step further by implementing an if-else-formula so that when an actor is removed the agent would update appropriately?
  2. How to construct an agent that copies/extracts some original-notes (no Aliases) that are link-connected (Link Type “example”) out of a big Map that contains hundreds of other original-notes link-connected using different Link Types?

(Mark Anderson) #8

Q1. Why. I took that old demo and deleted actor3’s original from the Parts container and the agent updated the map as I’d expect. I sense you’re trying to do something different but it’s not clear quite what that is - at least to me!

Q2. Agents can’t copy original notes, only move them. Moving is done by altering the $Container of the relevant original note. Or, referring to my old demo file, were ‘Parts’ a container (whether viewed as a map or not) of 100s of items the agent could simply have a more specific query to generate only the right aliases.

Pertinent points:

  • Agents can’t create new notes.
  • Agents can move originals of matched notes’ aliases by editing the original’s $Container
  • Agents can link aliases to notes or aliases to aliases but though needs to be given to ensure the right alias(es) are targeted.
  • You can turn off auto-cleanup of agent maps (via $CleanupAction) but beyond that layout becomes a manual task. This reflects that (historically at least) spatial hypertext maps were for user exploration of relationships rather than auto-generation of network diagrams.

(Andreas Grimm) #9

to Q1. I did not mean to delete actor3’s original from the Parts container but – within the “agent1” – to remove “actor3” from the the attribute “Actor” in “plan1”-Alias so that only “actor1” remains in “plan1” . Doing this, at least for me, does not remove the agree-link between “plan1” and “actor3”.

(Andreas Grimm) #10

to Q2. Taking @frr149’s example here in this forum I was wondering whether one could create an agent that “extracts” only those notes linked with the link type “Uses” by showing both the aliases of the originals as well as the link (=link typ “Uses”) without the need to relink them manually as they are already originally linked.

(Mark Anderson) #11

Q1. OK, I see now. The code you need is:

linkTo(find($Actor(that).contains($Name) & $IsAlias),"agree");
$MySet = links.outbound."agree".$Path;
	if($IsAlias(X) & linkedTo(X) & $Actor.contains($Name(X))==false) {

This works for me, building off the TBX you quote above and working (FWIW, using v7.2.1).

(Mark Anderson) #12

Q1Q2 - I think this is a feature request at this point. For good reasons, related to other Tinderbox features, basic links aren’t always shared by originals and their aliases. This raise problems in your use case as it seems you want new aliases but which replicate links between other originals/aliases. I’m not arguing against the idea but simply stating I don’t believe it is possible with (simple) existing action code. Another issue is that height/width/Xpos/Ypos are intrinsic so would need to also be correctly ‘extracted’ and copied. Even than you can’t assume the auto-layout of link lines will replicate the source.

Put another way, it’s clear what you want the end point to look like but it’s less clear howe you’d do that with existing functions.

Edit: corrected reference to wrong preceding question (as noted in posts below)

(Andreas Grimm) #13

to Q1 --> actually Q2: I see what you mean, @mwra. Thanks for looking at this and sharing your thorough thoughts.

(May I suggest to rename “Q1” to “Q2” to clarify things for future forum-readers.)

(Andreas Grimm) #14

to Q1. Thank you ever so much, @mwra. Works beautifully. Now it’s time for me to sit down and deconstruct and understand what code you’ve written here.

(Mark Anderson) #15

The new part is this:

$MySet = links.outbound."agree".$Path;
	if($IsAlias(X) & linkedTo(X) & $Actor.contains($Name(X))==false) {

Edit - removed unintended blank line from code sample

Why $MySet? Firstly, from vague memory, using lists generated by links() tended to need evaluating or caching into an attribute before further use. So here I past the list of output from links() into a Set-type attribute. Of course you could use a more appropriate attribute name is you want, or already use $MySet for something else. I use a Set rather than a list as this de-dupes the links() output - i.e. we don’t have to worry about possible dupes further into the process.

Why gather $Path rather than $Name? Remember we’re trying to work with aliases. For the latter, the title is not unique (being shared with its original) but the path is. Thus we have a de-duped list of paths.

Now we iterate the list using the .each() operator. The if() test is:

if the tested object X is an alias AND if the currently processed alias links to X AND _the $Name of X is NOT is a value in the current aliases $Actors

IOW, if all true we have found a link that needs to be deleted - or rather any link of type ‘agree’ needs to be deleted. This is done using unlink(). Lastly we reset $MySet to nothing so lots of loop-process data isn’t stored unnecessarily.

We can actually improve this a bit more… (new post)

(Mark Anderson) #16

We really only want to process ‘plan’ objects rather than every alias is the agent. We can’t narrow the agent query as we want aliases of the actor notes. So we wrap the agent action in a test of prototype:

	linkTo(find($Actor(that).contains($Name) & $IsAlias),"agree");
	$MySet = links.outbound."agree".$Path;
		if($IsAlias(X) & linkedTo(X,"agree") & $Actor.contains($Name(X))==false) {

The extra change is in the list ‘.each’ evaluation where we add the desired link type to the linkedTo() query term for even finer control. so not only must the conditions described in my last post be met but the link must be specifically of the link type ‘agree’. Thus if the link type were ‘disagree’ and all other conditions were the same the if condition would evaluate as false.

Why bother with link types? Bear in mind - even if not in this exact case - you may actually want other links - of the same direction and source/target for other reasons. By using a link type we know we are acting only on the links associated with this part of the process and not disturbing anything else. Indeed, if the links are there to enable you to compute values via the hypertext (links), then once tested you may chose to hide (that type of) links. I used the built-in link type ‘agree’ for rapid testing purposes; for your own project you might prefer a more descriptive name. If so, that’s fine, just don’t forget to use you type to replace ‘agree’ in the code.

I hope that helps explain a bit more of the how and why of the additional code and to help others to take the concept and adapt it to other uses.

Edit:a couple of minor typos (but not in the code!).

(Andreas Grimm) #17

Great, @mwra!

And yet: Now, I need to look at this and digest what you suggest – before I can possibly reply.

Thank you very much.