Index: trunk/Ohana/src/opihi/pantasks/CheckJobs.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 4697)
@@ -29,6 +29,6 @@
 	/* XXX this will break on 0 values in output streams */
 	if (VerboseMode()) fprintf (stderr, "job %s (%d) crash\n", job[0].task[0].name, job[0].JobID);
-	set_str_variable ("stdout", job[0].stdout.buffer);
-	set_str_variable ("stderr", job[0].stderr.buffer);
+	PushNamedQueue ("stdout", job[0].stdout.buffer);
+	PushNamedQueue ("stderr", job[0].stderr.buffer);
 	if (job[0].task[0].crash != NULL) {
 	  exec_loop (job[0].task[0].crash);
@@ -40,6 +40,6 @@
       case JOB_EXIT:
 	if (VerboseMode()) fprintf (stderr, "job %s (%d) exit\n", job[0].task[0].name, job[0].JobID);
-	set_str_variable ("stdout", job[0].stdout.buffer);
-	set_str_variable ("stderr", job[0].stderr.buffer);
+	PushNamedQueue ("stdout", job[0].stdout.buffer);
+	PushNamedQueue ("stderr", job[0].stderr.buffer);
 	/* run corresponding task[0].exit macro, if it exists */
 	macro = job[0].task[0].def;
Index: trunk/Ohana/src/opihi/pantasks/CheckTasks.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 4697)
@@ -22,4 +22,5 @@
       if (!status) continue;
     }
+    if (!ValidateTask (task, TRUE)) continue;
 
     /* construct job from task */
Index: trunk/Ohana/src/opihi/pantasks/ControllerOps.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 4697)
@@ -3,4 +3,5 @@
 # define CONNECT_TIMEOUT 300
 
+/* local static variables to hold the connection to the controller */
 static int status = FALSE;
 static int stdin_cntl, stdout_cntl, stderr_cntl;
@@ -8,8 +9,10 @@
 static IOBuffer stderr_buffer;
 
+/* test if the controller is running */
 int CheckControllerStatus () {
   return (status);
 }
 
+/* check job / get output if done */
 int CheckControllerJob (Job *job) {
 
@@ -23,4 +26,5 @@
 }
 
+/* ask controller about job status */
 int CheckControllerJobStatus (Job *job) {
 
Index: trunk/Ohana/src/opihi/pantasks/LocalJob.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/LocalJob.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/LocalJob.c	(revision 4697)
@@ -1,3 +1,102 @@
 # include "psched.h"
+
+/* local jobs are forked in the background 
+   we might need to limit the maximum number of local jobs.
+   should we have a queue/stack of pending local jobs, much
+   like controller? */
+
+/* update current state, drain stdout/stderr buffers */
+int CheckLocalJob (Job *job) {
+
+  int Nread;
+
+  // XXX do something useful with exit status?
+  CheckLocalJobStatus (job);
+
+  if ((job[0].state == JOB_EXIT) || (job[0].state == JOB_CRASH)) {
+    EmptyIOBuffer (&job[0].stdout, 10, job[0].stdout_fd);
+    EmptyIOBuffer (&job[0].stderr, 10, job[0].stderr_fd);
+    close (job[0].stdout_fd);
+    close (job[0].stderr_fd);
+  } else {
+    /* read stdout buffer */
+    Nread = ReadtoIOBuffer (&job[0].stdout, job[0].stdout_fd);
+    switch (Nread) {
+      case -2:  /* error in read (programming error?  system level error?) */
+	fprintf (stderr, "serious IO error\n");
+	exit (2);
+      case -1:  /* no data in pipe */
+      case 0:   /* pipe is closed, change child state? **/
+      default:  /* data in pipe */
+	break;
+    }
+  
+    /* read stderr buffer */
+    Nread = ReadtoIOBuffer (&job[0].stderr, job[0].stderr_fd);
+    switch (Nread) {
+      case -2:  /* error in read (programming error?  system level error?) */
+	fprintf (stderr, "serious IO error\n");
+	exit (2);
+      case -1:  /* no data in pipe */
+      case 0:   /* pipe is closed, change child state? **/
+      default:  /* data in pipe */
+	break;
+    }
+  }
+  return (TRUE);
+}
+
+int CheckLocalJobStatus (Job *job) {
+
+  int result, waitstatus;
+
+  /* check local job status */
+  result = waitpid (job[0].pid, &waitstatus, WNOHANG);
+  switch (result) {
+    case -1:  /* error with waitpid */
+      switch (errno) {
+	case ECHILD:
+	  fprintf (stderr, "unknown PID, not a child proc\n");
+	  fprintf (stderr, "did process already exit?  programming error?\n");
+	  job[0].state = JOB_NONE;
+	  job[0].exit_status = 0;
+	  return (FALSE);
+	case EINVAL:
+	  fprintf (stderr, "error EINVAL (waitpid): programming error\n");
+	  exit (1);
+	case EINTR:
+	  fprintf (stderr, "error EINTR (waitpid): programming error\n");
+	  exit (1);
+	default:
+	  fprintf (stderr, "unknown error for waitpid (%d): programming error\n", errno);
+	  exit (1);
+      }
+      break;
+      
+    case 0:  /* process not exited */
+      job[0].state = JOB_BUSY;
+      job[0].exit_status = 0;
+      return (TRUE);
+
+    default:
+      if (result != job[0].pid) {
+	fprintf (stderr, "waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, job[0].pid);
+	exit (1);
+      }
+      if (WIFEXITED(waitstatus)) {
+	job[0].state = JOB_EXIT;
+	job[0].exit_status = WEXITSTATUS(waitstatus);
+      }
+      if (WIFSIGNALED(waitstatus)) {
+	job[0].state = JOB_CRASH;
+	job[0].exit_status = WTERMSIG(waitstatus);
+      }
+      if (WIFSTOPPED(waitstatus)) {
+	fprintf (stderr, "waitpid returns 'stopped': programming error\n");
+	exit (1);
+      }
+  }
+  return (FALSE);
+}
 
 /* this could be written a just a one-way pipe */
@@ -58,99 +157,5 @@
 }
 
-/* update current state, drain stdout/stderr buffers */
-int CheckLocalJob (Job *job) {
-
-  int Nread;
-
-  // XXX do something useful with exit status?
-  CheckLocalJobStatus (job);
-
-  if ((job[0].state == JOB_EXIT) || (job[0].state == JOB_CRASH)) {
-    EmptyIOBuffer (&job[0].stdout, job[0].stdout_fd);
-    EmptyIOBuffer (&job[0].stderr, job[0].stderr_fd);
-  } else {
-    /* read stdout buffer */
-    Nread = ReadtoIOBuffer (&job[0].stdout, job[0].stdout_fd);
-    switch (Nread) {
-      case -2:  /* error in read (programming error?  system level error?) */
-	fprintf (stderr, "serious IO error\n");
-	exit (2);
-      case -1:  /* no data in pipe */
-      case 0:   /* pipe is closed, change child state? **/
-      default:  /* data in pipe */
-	break;
-    }
-  
-    /* read stderr buffer */
-    Nread = ReadtoIOBuffer (&job[0].stderr, job[0].stderr_fd);
-    switch (Nread) {
-      case -2:  /* error in read (programming error?  system level error?) */
-	fprintf (stderr, "serious IO error\n");
-	exit (2);
-      case -1:  /* no data in pipe */
-      case 0:   /* pipe is closed, change child state? **/
-      default:  /* data in pipe */
-	break;
-    }
-  }
-  return (TRUE);
-}
-
-int CheckLocalJobStatus (Job *job) {
-
-  int result, waitstatus;
-
-  /*** if child has exited, does the pipe hang around until it is flushed? ***/
-  /*** who closes the child stdout/stderr fd? */
-
-  /* check local job status */
-  result = waitpid (job[0].pid, &waitstatus, WNOHANG);
-  switch (result) {
-    case -1:  /* error with waitpid */
-      switch (errno) {
-	case ECHILD:
-	  fprintf (stderr, "unknown PID, not a child proc\n");
-	  fprintf (stderr, "did process already exit?  programming error?\n");
-	  job[0].state = JOB_NONE;
-	  job[0].exit_status = 0;
-	  return (FALSE);
-	case EINVAL:
-	  fprintf (stderr, "error EINVAL (waitpid): programming error\n");
-	  exit (1);
-	case EINTR:
-	  fprintf (stderr, "error EINTR (waitpid): programming error\n");
-	  exit (1);
-	default:
-	  fprintf (stderr, "unknown error for waitpid (%d): programming error\n", errno);
-	  exit (1);
-      }
-      break;
-      
-    case 0:  /* process not exited */
-      job[0].state = JOB_BUSY;
-      job[0].exit_status = 0;
-      return (TRUE);
-
-    default:
-      if (result != job[0].pid) {
-	fprintf (stderr, "waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, job[0].pid);
-	exit (1);
-      }
-      if (WIFEXITED(waitstatus)) {
-	job[0].state = JOB_EXIT;
-	job[0].exit_status = WEXITSTATUS(waitstatus);
-      }
-      if (WIFSIGNALED(waitstatus)) {
-	job[0].state = JOB_CRASH;
-	job[0].exit_status = WTERMSIG(waitstatus);
-      }
-      if (WIFSTOPPED(waitstatus)) {
-	fprintf (stderr, "waitpid returns 'stopped': programming error\n");
-	exit (1);
-      }
-  }
-  return (FALSE);
-}
-
+/* should this function close the fd's? */
 int KillLocalJob (Job *job) {
 
Index: trunk/Ohana/src/opihi/pantasks/TaskOps.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/TaskOps.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/TaskOps.c	(revision 4697)
@@ -68,5 +68,9 @@
       fprintf (stderr, "- ");
     }
-    fprintf (stderr, "%-15s %4d   %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, tasks[i][0].argv[0]);
+    if (tasks[i][0].argv == NULL) {
+      fprintf (stderr, "%-15s %4d   %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, "dynamic");
+    } else {
+      fprintf (stderr, "%-15s %4d   %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, tasks[i][0].argv[0]);
+    }
     if (verbose) {
       fprintf (stderr, "    spawn period: %f, polling period: %f, timeout period: %f\n", 
@@ -209,15 +213,30 @@
 }
 
-int ValidateTask (Task *task) {
-
-  if (task[0].argc == 0) {
+int ValidateTask (Task *task, int RequireStatic) {
+
+  int i, hash;
+
+  /* is a static command defined? */
+  if (task[0].argc != 0) {
+    if (task[0].argv == NULL) {
+      fprintf (stderr, "task command arguments not defined (programming error)\n");
+      return (FALSE);
+    }
+    return (TRUE);
+  }
+  if (RequireStatic) {
     fprintf (stderr, "task command not defined\n");
     return (FALSE);
   }
-  if (task[0].argv == NULL) {
-    fprintf (stderr, "task command arguments not defined (programming error)\n");
-    return (FALSE);
-  }
-  return (TRUE);
+
+  /* no static command; dynamic command? */
+  if (task[0].exec != NULL) {
+    for (i = 0; i < task[0].exec[0].Nlines; i++) {
+      hash = TaskHash (task[0].exec[0].line[i]);
+      if (hash == TASK_COMMAND) return (TRUE);
+    }
+  }
+  fprintf (stderr, "task command not defined\n");
+  return (FALSE);
 }
 
@@ -250,4 +269,35 @@
 }
 
+Task *GetActiveTask () {
+  Task *task;
+  if (ActiveTask < 0) return (NULL);
+  task = tasks[ActiveTask];
+  return (task);
+}
+
+int TaskHash (char *input) {
+  
+  int hash;
+  char *command;
+
+  hash = TASK_NONE;
+
+  command = thisword (input);
+  if (command == NULL) return (TASK_EMPTY);
+
+  if (command[0] == '#')                  hash = TASK_COMMENT;
+  if (!strcasecmp (command, "END"))       hash = TASK_END;
+  if (!strcasecmp (command, "HOST"))      hash = TASK_HOST;
+  if (!strcasecmp (command, "NMAX"))      hash = TASK_NMAX;
+  if (!strcasecmp (command, "TRANGE"))    hash = TASK_TRANGE;
+  if (!strcasecmp (command, "COMMAND"))   hash = TASK_COMMAND;
+  if (!strcasecmp (command, "PERIODS"))   hash = TASK_PERIODS;
+  if (!strcasecmp (command, "TASK.EXIT")) hash = TASK_EXIT;
+  if (!strcasecmp (command, "TASK.EXEC")) hash = TASK_EXEC;
+
+  free (command);
+  return (hash);
+}
+
 /*** task timer functions ***/
 
Index: trunk/Ohana/src/opihi/pantasks/task.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/task.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/task.c	(revision 4697)
@@ -2,9 +2,7 @@
 # define prompt "> "
 
-enum {TASK_NONE, TASK_EMPTY, TASK_COMMENT, TASK_NMAX, TASK_TRANGE, TASK_END, TASK_HOST, TASK_COMMAND, TASK_PERIODS, TASK_EXIT, TASK_EXEC};
-
 int task (int argc, char **argv) {
 
-  int N;
+  int N, hash;
   int ThisList, status;
   char *input, *outline;
@@ -66,5 +64,6 @@
 
     stripwhite (input);
-    switch (TaskHash (input)) {
+    hash = TaskHash (input);
+    switch (hash) {
 
       case TASK_EMPTY:
@@ -78,5 +77,5 @@
 	free (input);
 	/* validate the new task: all mandatory elements defined? */ 
-	if (!ValidateTask (task)) {
+	if (!ValidateTask (task, FALSE)) {
 	  DeleteNewTask ();
 	  return (FALSE);
@@ -107,26 +106,2 @@
   return (FALSE);
 }
-
-int TaskHash (char *input) {
-  
-  int hash;
-  char *command;
-
-  hash = TASK_NONE;
-
-  command = thisword (input);
-  if (command == NULL) return (TASK_EMPTY);
-
-  if (command[0] == '#')                  hash = TASK_COMMENT;
-  if (!strcasecmp (command, "END"))       hash = TASK_END;
-  if (!strcasecmp (command, "HOST"))      hash = TASK_HOST;
-  if (!strcasecmp (command, "NMAX"))      hash = TASK_NMAX;
-  if (!strcasecmp (command, "TRANGE"))    hash = TASK_TRANGE;
-  if (!strcasecmp (command, "COMMAND"))   hash = TASK_COMMAND;
-  if (!strcasecmp (command, "PERIODS"))   hash = TASK_PERIODS;
-  if (!strcasecmp (command, "TASK.EXIT")) hash = TASK_EXIT;
-  if (!strcasecmp (command, "TASK.EXEC")) hash = TASK_EXEC;
-
-  free (command);
-  return (hash);
-}
Index: trunk/Ohana/src/opihi/pantasks/task_command.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/task_command.c	(revision 4693)
+++ trunk/Ohana/src/opihi/pantasks/task_command.c	(revision 4697)
@@ -14,6 +14,9 @@
   task = GetNewTask ();
   if (task == NULL) {
-    fprintf (stderr, "ERROR: not defining or running a task\n");
-    return (FALSE);
+    task = GetActiveTask ();
+    if (task == NULL) {
+      fprintf (stderr, "ERROR: not defining or running a task\n");
+      return (FALSE);
+    }
   }
 
