diff --git a/VERSION b/VERSION
index 850e7424..141f2e80 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.14.0
+1.15.0
diff --git a/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy b/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy
index 604fb15c..ef6097bc 100644
--- a/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy
+++ b/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy
@@ -495,7 +495,8 @@ abstract class BaseGroovyScript extends Script {
     Binding binding = getBinding()
     if (binding.hasVariable('__doc__')) {
       def doc = binding.getVariable('__doc__')
-      return doc.get(obj as String)
+      Agent a = binding.hasVariable('__agent__') ? binding.getVariable('__agent__') : null
+      return doc.get(a, obj as String)
     }
     return null
   }
@@ -509,7 +510,8 @@ abstract class BaseGroovyScript extends Script {
     Binding binding = getBinding()
     if (binding.hasVariable('__doc__')) {
       def doc = binding.getVariable('__doc__')
-      return doc.get()
+      Agent a = binding.hasVariable('__agent__') ? binding.getVariable('__agent__') : null
+      return doc.get(a)
     }
     return null
   }
diff --git a/src/main/java/org/arl/fjage/shell/Documentation.java b/src/main/java/org/arl/fjage/shell/Documentation.java
index 2024c03e..89b29368 100644
--- a/src/main/java/org/arl/fjage/shell/Documentation.java
+++ b/src/main/java/org/arl/fjage/shell/Documentation.java
@@ -13,13 +13,20 @@
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.arl.fjage.*;
+import org.arl.fjage.param.*;
 
 public class Documentation {
 
   protected List<String> doc = new ArrayList<String>();
-  protected Map<String,Integer> ndx = new HashMap<String,Integer>();
-  protected Pattern heading = Pattern.compile("^#+ +([^ ]+) +-.*$");
-  protected Pattern section = Pattern.compile("^#+ .*$");
+  protected int staticSize = 0;
+
+  protected static final Pattern heading = Pattern.compile("^#+ +([^ ]+) +-.*$");
+  protected static final Pattern section = Pattern.compile("^#+ .*$");
+  protected static final String crlf = "\\r?\\n";
+  protected static final String header = "^#+ +";
+  protected static final String gaps = "\\n+$";
+  protected static final String placeholder = "@@";    // to be replaced by agent name
 
   /**
    * Add markdown documentation.
@@ -27,67 +34,87 @@ public class Documentation {
    * @param s multiline string.
    */
   public void add(String s) {
-    String[] lines = s.split("\\r?\\n");
-    for (String line: lines) {
-      Matcher m = heading.matcher(line);
-      if (m.matches()) ndx.put(m.group(1), doc.size());
+    if (doc.size() > staticSize) doc.subList(staticSize, doc.size()).clear();
+    String[] lines = s.split(crlf);
+    for (String line: lines)
       doc.add(line);
-    }
+    staticSize = doc.size();
   }
 
   /**
    * Get documentation index.
    */
-  public String get() {
+  public String get(Agent agent) {
+    build(agent);
+    List<String> topics = new ArrayList<String>();
+    for (String s: doc)
+      if (s.startsWith("# ")) topics.add(s.substring(2));
+    topics.sort(null);
     StringBuilder sb = new StringBuilder();
-    for (Integer v: ndx.values()) {
-      String s = doc.get(v);
-      if (s.startsWith("# ")) {
-        sb.append(s.substring(2));
-        sb.append('\n');
-      }
+    for (String s: topics) {
+      sb.append(s);
+      sb.append('\n');
     }
     return sb.toString();
   }
 
+  /**
+   * Get documentation by keyword.
+   *
+   * @param keyword keyword.
+   */
+  public String get(Agent agent, String keyword) {
+    build(agent);
+    int count = 0;
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < doc.size(); i++) {
+      String s = doc.get(i);
+      Matcher m = heading.matcher(s);
+      if (m.matches() && m.group(1).equals(keyword)) {
+        extract(sb, i);
+        count++;
+      }
+    }
+    if (count == 0) search(sb, keyword);
+    return sb.toString().replaceAll(gaps, "\n");
+  }
+
   /**
    * Search documentation topics by keyword.
    *
+   * @param sb string builder.
    * @param keyword keyword.
    */
-  public String search(String keyword) {
+  protected void search(StringBuilder sb, String keyword) {
     keyword = keyword.toLowerCase();
     Set<String> topics = new HashSet<String>();
     String topic = null;
     for (String s: doc) {
       Matcher m = section.matcher(s);
-      if (m.matches()) topic = s.replaceAll("^#+ +", "- ");
+      if (m.matches()) topic = s.replaceAll(header, "- ");
       if (s.toLowerCase().contains(keyword)) topics.add(topic);
     }
-    if (topics.size() == 0) return null;
-    StringBuilder sb = new StringBuilder();
+    if (topics.size() == 0) return;
     sb.append("Possible topics:\n");
     for (String s: topics) {
       sb.append(s);
       sb.append('\n');
     }
-    return sb.toString();
   }
 
   /**
-   * Get documentation by keyword.
+   * Extract documentation.
    *
-   * @param keyword keyword.
+   * @param sb string builder.
+   * @param pos position.
    */
-  public String get(String keyword) {
-    Integer pos = ndx.get(keyword);
-    if (pos == null) return search(keyword);
+  protected void extract(StringBuilder sb, int pos) {
     String s = doc.get(pos);
     int level = s.indexOf(' ');
-    if (level <= 0) return null;
+    if (level <= 0) return;
     int endlevel = level;
-    StringBuilder sb = new StringBuilder();
-    sb.append(s.replaceAll("^#+ +", ""));
+    if (s.startsWith("# ")) sb.append(s);
+    else sb.append(s.replaceAll(header, ""));
     sb.append('\n');
     int skip = 0;
     for (int i = pos+1; i < doc.size(); i++) {
@@ -101,11 +128,11 @@ public String get(String keyword) {
         if (skip == 0 || level <= skip) {
           if (skip > 0) nl = true;
           skip = 0;
-          s = s.replaceAll("^#+ +", "");
+          s = s.replaceAll(header, "");
         }
         if (m.matches()) {
           skip = level;
-          sb.append("- ");
+          sb.append("* ");
           sb.append(s);
           sb.append('\n');
         } else if (nl) {
@@ -117,7 +144,34 @@ public String get(String keyword) {
         sb.append('\n');
       }
     }
-    return sb.toString().replaceAll("\\n+$", "\n");
+    sb.append('\n');
+  }
+
+  /**
+   * Build dynamic documentation by querying agents.
+   *
+   * @param agent agent.
+   */
+  protected void build(Agent agent) {
+    if (agent == null) return;
+    if (doc.size() > staticSize) doc.subList(staticSize, doc.size()).clear();
+    Container c = agent.getContainer();
+    AgentID[] agentIDs = c.getAgents();
+    for (AgentID a: agentIDs) {
+      ParameterReq req = new ParameterReq();
+      req.setRecipient(a);
+      req.get(new NamedParameter("__doc__"));
+      Message rsp = agent.request(req, 1000);
+      if (rsp != null && rsp instanceof ParameterRsp) {
+        Object docstr = ((ParameterRsp)rsp).get(new NamedParameter("__doc__"));
+        if (docstr != null) {
+          String s = docstr.toString().replaceAll(placeholder, a.getName());
+          String[] lines = s.split(crlf);
+          for (String line: lines)
+            doc.add(line);
+        }
+      }
+    }
   }
 
 }