Agent - ways to move an agent? - Action code => modify $Path?

As I’m getting more comfortable with using Tinderbox, I’m now exploring ways to bring some order in my notes. Making my first baby steps with using Agents.
For the time being, I’m working in outlook view mostly. (98%)

My questions:

  • how can I best re-locate an agent? What would be the way to modify $Path? Action code? If yes, where can I find some examples?
  • thinking path:
    • assuming that the results of an agent are shown as child-aliases beneath the agent, that also means that if I move an agent, I will also move the resulting aliases - Is this train of thought correct?
    • assuming we would prefer to gather “ALL” agents in the same place, e.g. tucked away within the Hints location, could I then “also” visualize these results elsewhere?
      Reasoning would be to later on look into what I can do with a dashboard, then showing the agent(s) in my preferred location.

I’ve experienced the impact of agents / automation on my Mac, (2018 mini, 32 GB ram) so I’m very much interested in learning:

  • to find where an agent or agents are
  • how to control their behaviour (cpu wise) by setting $AgentPriority?
  • what is the purpose of $CleanupAction

Thanks for helping out!

You can move an agent in exactly the same ways as other notes. In most documents, agents are comparatively few; moving them by dragging the agent where is ought to be is fine.

If you want to put all your agents in a single container, $Container is ideal.

Yes. This is also true of moving any container; dragging a container to a new place implies moving its descendants.

Thanks Mark!

$Container seems the way to go!

Also:
Can an Agent have an alias?

Agents can have aliases.

It’s worth noting that when you alias a container—note or agent—you don’t see its children (or agent child aliases), just an alias of the container. So, in Outline, if you make an alias of an agent and more the alias elsewhere in the outline, you only see the content of the agent’ note. To see its children, you have to go look at the original. It’s obvious once you know, but not necessarily beforehand.

Only agents have queries, so use this as a edict on a new note:

$Text = collect_if(all,$AgentQuery!="",$Path).format("\n");

This says:

  • check every note (all)
  • match if $AgentQuery is not empty, i.e. there is a query
  • for any matched item get its path
  • reformat the resulting list as a string with one list item per line
  • set that string as the text ($Text) of the note running this code

†. OK, smart adornments do but (a) we’re talking about using Outline view and (b) adornments can’t match an agent query (because reasons…).

1 Like

That behaviour is documented here: AgentPriority. Is there an aspect of that which you are unsure.

Tip: turn agents off ($AgentPriority) once run and the target notes are not changing. Once off an agent retains any matched aliases present when last run.

If you think agents are a performance issue this is most often a tell the you are using agents inefficiently. Likely much of the information for which you are making individual agents is catered for by one or several tabs using Attribute Browser view.

If you have 20 agents all search the same set of notes (be it all, or just a part of the TBX) and then applying one extra differing item, then the efficient way is to make one agent that search the wider set of notes. Then for the 20 individual conditions those search the first agent filtering for the individual condition.

Let’s say the TBX has 1000 notes of which the first query matches 100 notes. I the opening scenario, 20 agents search all is 20,000 searches per agent cycle. In the second method is is 1,000 + (20 x 100), i.e. 3,000. We’re now doing 15% of the number searches and still getting the same result, albeit with 21 agents. Attribute Browser is an even better method.

Tip: different views are still looking at the same data, just visualising it differently. Tabs in the background don’t do anything so replacing loads of single purpose agents with one Attribute Browser view tab removes a lot unneeded searching.

See Re-arrangeable Agent Maps

It’s worth trying out aTbRef Fuzzy Search as it might save you having to ask here. For example, open the search and type in ‘CleanupAction’ as the search term. as well as the above you’ll get 6 other results of notes also mentioning CleanupAction.

Hi Mark,

Thanks a lot for your additional info!
As mentioned, I’m making baby steps..

I’m not sure what you mean here..

In the mean time I’m glad that I’m finally breaking through and using Tinderbox! That is the main gist to me. At least now I can start “thinking” what / how to do things in Tinderbox.
I’m working through Agents and Dashboards too at the moment and noticed the quite large amount of Agents used there too.
Also I realise that Actions can be chained. But as said: step by step!

A huge thanks to both Marks for helping out!

Argh! Auto-correct strikes. The word (corrected above) is ‘performance’ not ‘preference’.

The reason I note this is a common pattern experienced is that s a user starts using agents and keeps adding more agents but never deleting any. Often these will be something like finding a value in $Tags. So, we successfully find one value. We add a new agent for a different value. Now we have 2 agents. But we have 50 tags and may end up with 50 agents. If we want to see which notes have which $Tags value(s) Attribute Browser view is perhaps a better approach.

Please don’t read this as critique. It is simply that as agents are often the first automation users try out, it can lead unintentionally to using more agents than makes sense (when other better but as yet undiscovered features would be better).

Tip: If adding an agent as a form of search there is no harm in deleting it once done. deletion remove the agent and its aliases but doesn’t affect other content. Coming soon, you will be able to use query-based search in the view Find bar. This should obviate the need for adding agents just for search.

If your agent use is more complex, then more information is needed to try and understand why you have a performance issue and whether it is the agents or something else that is the cause.

HTH :slight_smile:

1 Like

Hi Mark,

Firstly: thanks for helping out!
This works! :+1:

I’ve only been able to test the edict you kindly spelled out above as of today.
For what it’s worth, I’ve first implemented it, and then have started to “try” to “read” the edict in order to understand what we’re doing here, checking on aTbRef, to then later on read further on to find that my search was approx equivalent as your textual explanation. I know, this is a lot of words to say just this tiny bit :wink:

I think it will be very interesting to read and learn up on aTbRef > format to learn the powers that lay within..

For now I have a simple question:
How would we extend the edict to also list the $AgentPriority attribute?

Also: How complex would it be to format the output as a table?

As an aside: I find your explanation to be spot on with regards to the/my use of agents: likely too many. That is also one of the reasons that I’d like to extend this (maybe, if sufficiently sensible) with the use of an icon that would e.g. be colored according to $AgentPriority==level 0/1/4/10/20/-1.

Is it correct that @satikusala ‘s Training Video - Searching sets: understanding the .format dot operator treats this subject? Are there any other recommended Training Video’s or Meetup Video’s that treat this subject to learn about it?

Yes and note that there is a dot-operator version of this operator X.format() where X is the source data object (attribute, variable, designator, etc.). You might also want to look at list.format(formatStr).

I’ll assume you’d like to alter this edict:

$Text = collect_if(all,$AgentQuery!="",$Path).format("\n");

So, each agent’s $Path is still given, followed by its $Path, with a blank line between entries. There’s more than one way to do this. I’ll use a variable to build the content and then set $Text to that:

var:string vText=;
var:list vList =;
vList = collect_if(all,$AgentQuery!="",$Path);
vList.each(anItem){
   vText+="Agent path: "+$Path(anItem)+"\n";
   vText+="Agent query: "+$AgentQuery(anItem)+"\n\n";
}
$Text  = vText;

Like so:

Making a table? As a table in the styled text ($Text) of the note not possible I believe. But in preview, using either HTML or Markdown, not complex. But, you haven’t stated what the table will contain, so chick-and-egg, depending on how complex your table design the more work it might be.

Frankly, Attribute Browser view would be a far better way to do this, no edict needed:

I’ve blurred a control as I used the beta and that control is not in the current release.

Tada, table and listing all done with no extra coding!

Learning format()/.format(), I’m not sure what more there is to explain than in the documentation. For a multi-value list (List or Set), the operator needs a source list (either as an operator argument or dot-chained) and a formatting string. The list is then output as a single string, the formatting string being inserted between each value.

It might help if rather than seeking ‘more help’, forcing the helper to cover every possible use (a lot of possibilities!, i.e. their free time), you were to state which part you don’t understand. Also don’t be afraid to try and fail with some testing. Use a text TBX, not your main document. Sometimes, actually assembling the parts and running the code helps much more than paragraphs of explanatory text (or video). Also trying and failing gives an immediate bounded question: what you tried, in what context, and the result.

HTH :slight_smile:

1 Like

Note, I just updated the second grab so you can see the queries. If a column is too narrow you can drag it wider (there is no line wrap inside such info cells.)

1 Like

If you’re doing the common learning stage exercise of duplicating agents where the only changed aspect of the query’s code is search term:

$Tags.icontains("boats")
// or
$Tags.icontains("trains")
// or
$Tags.icontains("planes")
// etc.

Then container putting the search word in the agent’s $MyString and adding that attribute to the agents Displayed Attributes. Then set the agents code to use that value instead of a literal string, so the example above becomes:

$Tags.icontains($MyString(agent))

Now, editing $MyString in the agent sets a new target value. In AB view (as described above, not you only need to list $MyString values for agents as the code in each case is the same, only the target value changes. If you use a prototype for all these agents then you only need to set the query and Displayed Attributes once. If also aids he following…

Want to ensure the agent is only testing values from $Tags? First make a new String-type user attribute ‘AgentSearchString’, and set that as a Displayed Attribute instead of ‘MyString’. Now in the agent prototype, add this to the $Edict:

attribute("AgentSearchString")["suggested"] = values("Tags");

Ans the agent prototype’s query is now :

$Tags.icontains($AgentSearchString(agent))

When the edict runs, the Displayed Attributes pop-up value list for $AgentSearchString will show all (and only) values used in $Tags. So if the pop-up list is used to set an agent’s $AgentSearchString you know you will be using a value used in the $Tags of at least one note. Plus, AB view can now list discrete AgentSearchString values showing which tags are being searched for by an agent.

Of course part of the point of this abstraction work is that if one agent can easily re reconfigured to search got any current $Tags value, then if you have 50 tags you don’t need 50 agents. The latter is a trap early stage users fall into as re-use of agents do search, sequentially, for different values using the same logic is not envisaged as a possibility.

The early example uses a single term query. But, we might as easily use the ame term in a multi-term query:

$Tags.icontains($AgentSearchString(agent)) | $Name == $Name(AgentSearchString(agent)) | $Text.contains(AgentSearchString(agent))

I should add this doesn’t work for every agent use, but it does help where you re-use a query’s logic but for a different literal search value.

Firstly, my main goal is to learn to better understand action code in general, as after all a lot of Tinderbox use evolves around this. Hence my focus on code.
So indeed I wanted to elaborate on the edict that you wrote out so nicely:
$Text = collect_if(all,$AgentQuery!="",$Path).format("\n");

I’m working on gaining a better understanding of Agents, Stamps, Edicts, Rules, and in the course of events, this (at least for me) takes days if not weeks.

Question-1:
In your code below, you start with declaring two variables. I notice a difference in spelling between the two. Does this have any importance?
=> notice the space on the second line after vList =; (mind I have deliberately inserted 2 spaces here for clarity)

Question-2:
I have elaborated the Edict code like this, to show $AgentPriority:

var:string vText=;
var:list vList=;
vList = collect_if(all,$AgentQuery!="",$Path);
vList.each(anItem){
	vText+="Agent path: "+$Path(anItem)+"\n"
	vText+="Agent query: "+$AgentQuery(anItem)+"\n";
	vText+="AgentPriority: "+$AgentPriority(anItem)+"\n\n";
}
$Text = vText;

This edict code works but with some issues..
Now what is the issue?

  • The edict works, but the middle line “Agent query” is not shown
  • The first and third row are shown allright.
  • I have approximately 20 tuples (should be triples) that are “empty”

What can I learn from the 20 empty tuples?
I remember recently searching for the keyboard shortcut Shift + Cmd + A (create agent), might it be that I have inadvertently created 20 “empty” agents in this way?
I also know that if you create a new note and then do nothing and then start working on something else (within TBX) then the empty note disappears, so I assume that TBX will not allow for empty notes?

As for your advice on the Attribute Browser, I’ll definitely have a look at that one!

For completeness sake, I will paste the contents of $Text from the above edict:
At least that way you can see how the “empty” results are spread and looking.


Agent path: /aLearning agent - Channel = Meintechblog

AgentPriority: 1

Agent path: /TBX/aDateQuery

AgentPriority: 1

Agent path: /ToDo - still to view/aChannel - Meintechblog

AgentPriority: 4

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path: /Daily notes/Daily Note - 2026-03-26/aLearningAgent

AgentPriority: 1

Agent path: /Daily notes/Daily Note - 2026-03-26/aChannel - Gary Does Solar

AgentPriority: 4

Agent path: /Daily notes/Daily Note - 2026-04-01/aCollect TBX forum posts

AgentPriority: 20

Agent path: /Daily notes/Daily Note - 2026-04-01/aCollect - Gary Does Solar

AgentPriority: 1

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path:

AgentPriority:

Agent path: /aSchatten PV

AgentPriority: 10

Agent path: /aCollect aTbRef

AgentPriority: 10

Agent path: /aCollect aTbRef/aCollect aTbRef

AgentPriority: 10

Agent path: /aSearch Tag Victron

AgentPriority: 20

The $Path line inside your .each() loop lacks its terminating semicolon.

1 Like

Thanks Mark!
I probably need a special pair of “colon” enhanced spectacles / glasses!
And that even after scrutinizing the code…

Any idea how I have to interprete the 20 empty edict yieldings?

I’m confused, I definitely did not vary the spelling the variables. Note that each variables must have a unique name and perhaps that is what you’ve missed. My code declares two such variables: vString and `vList. The older usage would be:

var vText;
var vList;

Here I do two extra explicit things, I give each an elicit data type, so the app doesn’t have to guess our intent:

var:string ...
var:list ...

and I set both to the per-type default using =;, as is documented here: Setting an attribute to (no value) via code.

vList is used to hold the result of using collect() which returns … List-type data. Using a variable adds a line of text but, I’d hoped adds more clarity for the learner as to the type of data being passed around.

vText is a String-type variable used to assemble the overall string of ‘text’ we be passing to $Text when done. Whether you make one string and set $Text to it, to add iteratively to $Text is really personal choice. You get to the same output.

Did you see the article on the `var’ operator, here: var? If so, where did you get stuck?

On the wider point of ‘understanding’ action code…

‘Action code’ as a term can be confusing due to 20 years of iterative growth.

At outset ‘Actions’ were changes made by notes to themselves ($Rule) by or containers to their (new) children; container ($OnAdd), agent ($AgentAction). Stamps are ad-hoc calls to actions applied on the current selection. In the early days (2000s) these were written mainly in export code (marked with a ^ caret, e.g. ^if()^) or in query code (no longer used).

Things expanded two ways:

  • The scope of ‘actions’ increased both in where/how that could be applied and in the power of what they could do.
  • The operators/syntax used for this became ‘action code’.

The older ^code^ receded to be being used only for export templates and even then, in export code many export code operators were replaced by action code equivalents used as arguments for export code operators. Compare old export code (c. v3) with now (v11). Similarly, actions then vs. now. In addition, the ‘query code’ used to write agent queries and conditional (‘if’) statements is all now part of action code (the old query code no longer works).

Tinderbox has laudable support in terms of not making new advances break old code. Few people want to have to re-write working code just so it doesn’t break and Tinderbox is now 25 years old—dinosaur age in normal software terms. It also means if you copy code from 10 or 20 years back from old demos, blogs, etc., likely the code syntax has changed.

Frankly you have two approaches:

  • “I don’t want to code”. learn the basic tenets: set-colons at the end f expressions, $-prefixes when referencing attribute value, putting quotes around string values. then much of the rest can be copy space, applying the rules to check fro things you 'broke when editing the example for re-use. When you see something described as ‘optional’, ignore it as it is not talking to you, but the next type of user, below. Copy/paste means following the rules even if verbose for the expert; as a copy/paster you’re never going to be an expert so the options are just an avenue for failure.
    • This is why code examples in aTbRef are generally over explicit. Experts can prune and use shorter code, but in truth code brevity or aesthetics or layout as not the immediate challenge of the copy/paster. The latter is not a second-class Tinderbox citizen but the term simply reflects that ‘code’ is something many find annoying and confusing.
  • “I want to understand how action code works”. Read the getting started Help PDF then all of —skimming on first pass—Action Code. As that point you’ll have an overview of action code and some of its oddities (many reflecting its long life).

Either way, if offered a choice or new or old code, 99.9% of users should choose the newer version.

Thanks for elaborating - will study this in depth.
Maybe I used too many words:
See the extra space below in the var declaration - That what my (confusing) question was about.. :wink: