Background
Maka's current Bash tool works well for foreground one-shot commands: it executes a command, streams output, retains a bounded tail, handles timeout/cancel, and emits terminal runtime events.
Some tasks do not naturally fit into a single tool call:
- starting a dev server or test server;
- starting watch mode;
- starting a local service and waiting for readiness;
- running a long benchmark or harness job;
- observing logs over time before a later step makes a decision.
Without a runtime-owned long-running shell lifecycle, agents are pushed toward shell tricks such as &, nohup, tmux, redirected log files, hand-written pid files, and ad hoc log polling. Those can work for an individual task, but they bypass the runtime contracts Maka already has.
Problem
The core problem is not just "make Bash return asynchronously". The missing concept is an observable, cancellable, waitable, repairable shell process resource owned by the runtime.
Shell tricks introduce structural problems:
ToolRuntime only owns the initial launch, not the later lifecycle;- permission, approval, and trace only describe the initial command and cannot model later wait/cancel operations;
- stdout/stderr bounding and redaction-safe tail handling become script conventions;
- UI, read models, and the
AgentRun ledger cannot reliably represent that a command is still running; - startup recovery sees incomplete history instead of a runtime fact that can be repaired deterministically;
- desktop and headless can drift into separate background-process semantics.
Direction
A likely better direction is to avoid modeling this as Bash(background: true).
Instead, a shell command could be promoted by the runtime into an observable ShellRun when it has not finished within the initial yield window.
The flow could look like this:
- The agent calls a shell command.
- The runtime waits for
yield_time_ms. - If the command exits, return the normal terminal result.
- If the command is still running, return
shellRunId, current output tail, and status. - Follow-up operations such as
wait, status, and cancel observe or control that ShellRun.
This direction has a few advantages:
- long-running behavior is an observed runtime fact, not something the model has to guess before launch;
- short foreground commands and long-running commands can share the same lower-level process lifecycle;
- permission, trace, output bounding, and startup repair can stay attached to Maka's runtime spine;
- the design can leave room for PTY or interactive sessions later without making all terminal semantics part of the first slice.
This is not meant as a final design. It is a proposed shape for discussion.
Questions To Settle
Should ShellRun become a new runtime fact, or should this extend the existing terminal runtime event model?
What should the agent-facing surface look like?
Possible options:
- keep
Bash, add yield_time_ms, and return shellRunId when the process is still running; - add a new
exec_command entry point for long-running shell work; - keep
Bash as a foreground one-shot tool and add separate shell_start, shell_wait, and shell_cancel tools.
Should P0 only cover pipe-mode non-interactive long-running processes?
PTY and interactive stdin probably need a larger ShellSession-level contract: screen state, resize, prompt handling, secret input, input auditing, and more complex recovery semantics. The lower-level lifecycle should not block future PTY support, but whether tty belongs in P0 needs a deliberate decision.
How should startup recovery be represented?
If Maka restarts and cannot prove the process is still observable, what terminal state should be recorded? Possible choices include orphaned, failed, or an existing run-repair state. The important property is a stable, testable policy that does not pretend to recover an OS process.
Should desktop and headless share the same lifecycle model from the first slice?
My inclination is yes, otherwise background shell can become a second fact model. If the implementation cost is too high, the shared contract could be defined first and wired incrementally.
Should P0 retain only bounded tails, or introduce a managed full-output artifact?
Bounded stdout/stderr tails may be enough for P0. Full output artifacts can be a follow-up unless benchmark or harness workflows need complete logs from the start.
Possible P0 Scope
A first slice could cover:
- shell command can yield while still running;
- runtime owns a
ShellRun; - status and bounded stdout/stderr tails can be read;
- wait is supported;
- cancel is supported and cleans up the process tree;
- runtime events / ledger facts represent the lifecycle explicitly;
- startup recovery has deterministic behavior for non-terminal
ShellRuns; - docs state that this is not a sandbox boundary and does not promise process survival across app restart.
Out Of Scope For P0
- full PTY screen model;
- interactive secret or prompt handling;
- remote observation API;
- recovering the OS process after app restart;
- treating
nohup, tmux, or disown as the product contract; - a headless-only job registry independent from the runtime ledger.
Possible Invariants
Whatever surface is chosen, the long-running shell mechanism should preserve these properties:
- runtime owns the lifecycle;
- output remains bounded and redaction-safe;
- cancel cleans up the process tree;
- missing terminal evidence cannot repair a related run to completed;
- desktop and headless do not grow drifting shell lifecycle models;
- recovery semantics are conservative, explicit, and testable.
Background
Maka's current
Bashtool works well for foreground one-shot commands: it executes a command, streams output, retains a bounded tail, handles timeout/cancel, and emits terminal runtime events.Some tasks do not naturally fit into a single tool call:
Without a runtime-owned long-running shell lifecycle, agents are pushed toward shell tricks such as
&,nohup,tmux, redirected log files, hand-written pid files, and ad hoc log polling. Those can work for an individual task, but they bypass the runtime contracts Maka already has.Problem
The core problem is not just "make Bash return asynchronously". The missing concept is an observable, cancellable, waitable, repairable shell process resource owned by the runtime.
Shell tricks introduce structural problems:
ToolRuntimeonly owns the initial launch, not the later lifecycle;AgentRunledger cannot reliably represent that a command is still running;Direction
A likely better direction is to avoid modeling this as
Bash(background: true).Instead, a shell command could be promoted by the runtime into an observable
ShellRunwhen it has not finished within the initial yield window.The flow could look like this:
yield_time_ms.shellRunId, current output tail, and status.wait,status, andcancelobserve or control thatShellRun.This direction has a few advantages:
This is not meant as a final design. It is a proposed shape for discussion.
Questions To Settle
Should
ShellRunbecome a new runtime fact, or should this extend the existing terminal runtime event model?What should the agent-facing surface look like?
Possible options:
Bash, addyield_time_ms, and returnshellRunIdwhen the process is still running;exec_commandentry point for long-running shell work;Bashas a foreground one-shot tool and add separateshell_start,shell_wait, andshell_canceltools.Should P0 only cover pipe-mode non-interactive long-running processes?
PTY and interactive stdin probably need a larger
ShellSession-level contract: screen state, resize, prompt handling, secret input, input auditing, and more complex recovery semantics. The lower-level lifecycle should not block future PTY support, but whetherttybelongs in P0 needs a deliberate decision.How should startup recovery be represented?
If Maka restarts and cannot prove the process is still observable, what terminal state should be recorded? Possible choices include
orphaned,failed, or an existing run-repair state. The important property is a stable, testable policy that does not pretend to recover an OS process.Should desktop and headless share the same lifecycle model from the first slice?
My inclination is yes, otherwise background shell can become a second fact model. If the implementation cost is too high, the shared contract could be defined first and wired incrementally.
Should P0 retain only bounded tails, or introduce a managed full-output artifact?
Bounded stdout/stderr tails may be enough for P0. Full output artifacts can be a follow-up unless benchmark or harness workflows need complete logs from the start.
Possible P0 Scope
A first slice could cover:
ShellRun;ShellRuns;Out Of Scope For P0
nohup,tmux, ordisownas the product contract;Possible Invariants
Whatever surface is chosen, the long-running shell mechanism should preserve these properties: