What is sub-tasking?
Sub-tasking is the ability for a task to spin off sub-tasks and wait for them to finish before potentially entering a “submitted” state themselves for an agent to pick them up. When creating subtasks, yourcreate_go_tasking
function will finish completing like normal (it doesn’t wait for subtasks to finish).
When a task has outstanding subtasks, its status will change to “delegating” while it waits for them all to finish.

Why do subtasks?
Subtasking provides a way to separate out complex logic into multiple discrete steps. For example, if a specific task you’re trying to do ends up with a complex series of steps, then it might be more beneficial for the agent developer and operator to see them broken out. For example, apsexec
command actually involves a lot of moving pieces from making sure that you:
- have a service executable (or some sort-lived task that is ok to get killed by the service control manager)
- can access the remote file system
- can write to the remote file system in some way (typically smb)
- can create a scheduled task
- can delete the scheduled task
- can remove the remote file
How to create subtasks?
Creating subtasks are pretty easy:create_go_tasking
or even task callbacks (in the next section). We’re specifying the name of the command to run along with the parameters to issue (as a string). We can even specify a SubtaskCallbackFunction
to get called within our current task when the subtask finishes. It’s a way for the parent task to say “when this subtask is done, call this function so I can make more decisions based on what happened”. These callback functions look like this:
Notice how this function’s parameters don’t start with
self
. This isn’t a function in your command class, but rather a function outside of it. With the data passed in via the PTTaskCompletionFunctionMessage
you should still have all you need to do MythicRPC* calls though.PTTaskCompletionFunctionMessage
has all the normal information you’d expect for the parent task (just like you’d see in your create_go_tasking
function) as well as all the same information for your subtask
. This makes it easy to manipulate both tasks from this context.Note: If you want to modify the information about your current task, some fields can be updated via the
PTTaskCompletionFunctionMessageResponse
, but otherwise you’ll need to use the SendMythicRPCTaskUpdate
function call to update other attributes about your parent task (or anything about your subtask).If you’re creating subtasks and you want tokens associated with them (such as matching the token supplied for the parent task), then you must manually supply it as part of creating your subtask (ex:
Token=taskData.Task.TokenID
). Mythic doesn’t assume subtasks also need the token applied.What does the flow look like?
Here we have the flow for a command,shell
, that issues a subtask called run
and registers two completion handlers - one for when run
completes and another for when shell
completes. Notice how execution of shell
’s create tasking function continues even after it issues the subtask run
. That’s because this is all asynchronous - the result you get back from issuing a subtask is only an indicator of if Mythic successfully registered the task to not, not the final execution of the task.

What are task callbacks?
Task callbacks are functions that get executed when a task enters a “completed=True” state (i.e. when it completes successfully or encounters an error). These can be registered on a task itselfcompleted_functions
attribute like:
key
is the same name of the function specified and the value
is the actual reference to the function to call.
Where are they?
Like everything else associated with a Command, all of this information is stored in your command’s Python/GoLang file. Sub-tasks are created via RPC functions from within your command’screate_tasking
function (or any other function - i.e. you can issue more sub-tasks from within task callback functions). Let’s look at what a callback function looks like:
Task Callbacks
This is useful for when you want to do some post-task processing, actions, analysis, etc when a task completes or errors out. In the above example, theformulate_output
function simply just displays a message to the user that the task is done. In more interesting examples though, you could use the get_responses
RPC call like we saw above to get information about all of the output subtasks have sent to the user for follow-on processing.