Agent to collect all reference notes with links to notes in a specific container

Dear Tinderbox Family! I am fairly new to Tinderbox (bought a license in January), hence please don’t judge my lack of knowledge :slight_smile:


I am a scholar and university teacher and organise my course work in a tinderbox file. I have a container in which I organise oral exams for this semester. In this container I collect the names of the attendees/students as containers and in those containers the notes per topic:

/Oral Exams Semester XYZ/Date-Student/Topic

I also have a container in which I collect all relevant References:


To each topic I have reference-type links pointing to the specific topics.


I would like to create an Agent to plan my reading tasks and that collects all relevant reference files. I thought I could collect all notes/reference files ($Prototype=“Reference”) that link (linking type: “Reference”) to any note in the Oral Exams container. I am pretty sure it is a rather simple task in Tinderbox but could maybe anyone help me figuring out the query code for the agent, as I have no clue where to start.

Greetings from Cologne :slight_smile:

So, you’re asking a several step question which has some edge and some starting assumptions:

  • we want container /Oral Exams Semester XYZ to hold a de-duplicated list of identifying info for all “Reference” (proto)typed notes with inbound links of type “Reference”.
  • The reference notes’ titles ($Name) are unique
  • The reference notes’ titles are path-safe

Let’s assume the first two assumptions are true but the last isn’t (because life’s like that). In the last case we’ll use $ID (or $IDString if you prefer) listed of $Path in the list we collect.

We can use collect_if(scope, condition, expressionStr) for this.</s?—forgot you wanted an agent, working on that now.

[Testing solution as it’s not the easiest of queries - more in a bit]

†. Although ‘Reference’ here is used as a literal in two discrete contexts (prototype name and link type) it’s courting a misunderstanding. A useful consideration, given that links are directional, is to use a link type that indicates purpose. So, you have a reference-type note linking to notes using that reference. So, ‘used by’ might be both a more descriptive term and less prone to possible misinterpretation (computers don’t see the data/context though human eyes).

‡. Annoyingly, papers—especially in the sciences) use characters in their titles that confuse Tinderbox’s path-parser. Consider a note called “this/that” at $Path /path/to/note/this/that: trying to use action code with that path will look for a note “that” in container ‘this’ at path /path/to/note/this. See further explanation at: Problematic Characters for Action code in $Name and $Path.

Thank you Mark! I am not sure wether I understand you starting assumptions correctly:

  • we want to have a list of all $Prototype==“Reference”-Files that link to notes (==children) in collection /Oral Exams Semester XYZ
  • the reference notes’ titles are unique (and for the sake of simplicity I might change them to their $BibTeXKey)
  • the reference notes are linked to the topics (==children of /Oral Exams XYZ) via a specific linking type called “Reference”

We can use collect_if(scope, condition, expressionStr) for this.

I am sorry, could you please point me to the right direction? I feel rather stupid right now. I thought maybe I could collect all notes that link via Reference link type (==outbound) to children of /Oral Exams XYZ.

Sorry, lots of things going on at once. I overlooked that you wanted an agent—working on a demo file now as it’s easier to explain with a worked example

Here’s work in progress:

I forgot to say, ‘welcome to the forum!’. TBX follows shortly. I’m deliberately not trying to change your process. If necessary, we can optimise after we’ve done the agent.

[Ugh: student Smitt will actually be taking topics B & C not C twice!]

1 Like

I think this makes good sense. I’d suggest breaking the task down into small, simple parts. Then you can do each part separately, checking that they do what you expect. And then, when the parts are all working separately, you connect them up and, voila!

Part 1: Is a note a reference, or something else?
Part 2: Is a note inside the Oral Exams container?
Part 3: Does a note have a link to a note inside the Oral Exams container?

For example, we could easily write a function that takes a note’s $Path and tells us whether it’s a reference. In fact, you’ve already done it!

function isReference(theNote:string) {
   return ($Prototype(theNote)=="Reference");

If we want a list of every reference, we could make that in an agent:

Query: isReference(this)

OK: using a function for this is perhaps gilding the lily, but I think functions might be helpful for the other parts and they make things a bit easier to test. (For example, if this agent finds all the references and doesn’t find other stuff, the function is probably right!)

I suggest you try Part 1 first. After that, Part 2 should be fairly straightforward. Do that, and we can focus on Part 3.

1 Like

woooah, thank you Mark! I did not expect to step my toes into functions at this stage of my TBX journey!

The function works, the Agent gives me as expected all Reference prototype notes in the TBX-document. Next step should be to figure out the scope (step 2). Yet, straight forward does not yet come so easily to me regarding TBX :slight_smile:

Here’s the task:

Part 2 : Is a note inside the Oral Exams container?

We know that the skeleton will be a function that takes a Path, as in Part 1, returns true or false.

function isOralExam(theNote:string) {
   var:Boolean result;
   result=....DO SOMETHING HERE....
   return result;

Now, how would we know if a note is inside (say) /OralExam? I can think of several ways!

  1. Get our container’s path, $Path(parent). Is it /OralExam?
  2. The operator inside(/OralExam) is true if this note (or an alias of this note) is a child of /OralExam.
  3. The operator descendedFrom(/OralExam) is true if this note (or an alias of this note) is descended from of /OralExam.

Each of these is subtly different, but it may be that any of them is equally good. Again, you can test your function by using it in an agent: does it collect the notes you intend, and no other notes?

[sorry for delayed reply, hit by a couple of long calls, but here’s my worked solution for you]

OK, this took some doing as your requirement for an agent to list the references took some doing. this is because within an agent query, there is a limit to the nested complexity of the query you can answer.

To match your need all these must be true:

  • the note is of prototype ‘Reference’
  • the reference is used by a topic (i.e. linked by a ‘Reference’-type link)
  • the linked topic must reside under container ‘Oral Exams Semester XYZ’

This is because not all references are necessarily used and not all topics using references are necessarily part of the current semester.

How do we do this?

  • I added a new user Set-type attribute ‘MySetA’ so that I could replicate $ID data stored in $MySet ad used for production work with $Name data stored in $MySetA.
  • I added a prototype ‘pTopic’ so I could easily apply code to all topics

The ‘pTopic’ has this rule:

// collect IDs of all refs linking to this note
$MySet = links.inbound."Reference".$ID;
// collect titles of all refs linking to this note
$MySetA = links.inbound."Reference".$Name;

Only if the note is an original (i.e. not an alias) does the main rule code run. Why? Bear in mind we define the topic once and then place an alias of it under each student. The result is each topic knows which references apply to it. Here the $MySetA data is only to help you understand the process

Next we make an agent which I have called ‘@Relevant Topics’ which has this query:

$Name(grandparent)==$MyString(agent) & $Prototype=="pTopic"

Where the agent’s $MyString is the name of the exam semester we wish to test, e.g. “Oral Exams Semester ABC” . The agent’s rule is:

// get the IDs of references used by current topics
// get the Names of references used by current topics
// set explanatory text for the agent
$Text = "For test/check purposes, the following "+$MySet.count+" references should be found by the agent '@References In Use':\n\n"+$MySetA.format("\n\n");

The only bit we need is the $MySet data, the rest is to help understand the process. So by changing $MyString in the agent we store the $IDs of the references in use. The resulting $Text of this first agent then looks like this:

Now we can use a second agent ‘@References In Use’ to find only the relevant references and we do it with this query:

$Prototype=="Reference"&$MySet("@Relevant Topics").contains($ID(original))

So we start by getting all Reference-type notes and then we only continue to match them if their original note’s $ID is in the list of IDs stored in the $MySet of the first agent.

In many cases, a single agent would suffice, but your scenario has too many contingent factors to resolve in a single query. For what it is worth, just listing the names of the references in a note’s $Text is significantly less work and can be done a number of ways, but that wasn’t the starting scenario.

Here’s more of the overall setup:

and here is the test file: RefListTest1.tbx (422.1 KB)

Hi there, I am as well. I’d be happy to have a private session with you. I can show you how I teach with Tinderbox. I don’t have time to sanitize it to make it public, but I can do it one-on-one with you. DM me if you’d like to setup a call.

Thank you @eastgate and @mwra! That is a lot of information to digest! And also thank you @satikusala! I appreciate all of your offers and suggestions and the time you took to help me solve my issue. I will need some time to wrap my head around your responses and soon will get back to you :slight_smile:

But please allow me to get a bit off-topic: I am daily following this community (and your excellent videos @satikusala) since December last year and it already helped me a lot to understand the potential of Tinderbox. And as @satikusala mentioned in some of his videos and meetups: My thinking is already changing :slight_smile: