Why I Keep APX Routines Boring on Purpose
When I first started wiring recurring work into APX, I made the same mistake I keep seeing in small automation systems: I asked one routine to do everything.
I wanted the model to draft the text, decide whether the output was good enough, publish it, maybe notify me somewhere else, and then feel intelligent while doing it. That sounded neat. In practice, it made the boundary fuzzy and the failures harder to read.
The lesson was simple: a routine should be boring on purpose. Text generation, orchestration, and delivery are different jobs. If I keep them separate, APX is easier to trust.
That idea fits the APC/APX split I keep coming back to. APC holds durable project context in the repo. APX handles local runtime work. A scheduled routine should respect the same boundary: one part describes what the project wants, another part executes it on this machine, and a final shell step delivers the result.
What a routine actually is
APX routines are scheduled tasks. The daemon checks the scheduler every 5 seconds and fires anything due. A routine has a kind, a schedule, an optional spec blob, and optional pre_commands and post_commands around the model step.
That shape matters because it already tells me the right mental model. A routine is not "the agent running forever." It is a small pipeline.
The kinds are explicit:
-
heartbeatfor a simple marker -
shellfor pure shell work -
exec_agentfor one model call with no tools -
super_agentfor multi-step work with tools -
telegramfor a fixed message
That list is the first hint that APX wants me to choose the smallest tool that fits. It does not want every recurring job to become a mini-autonomous system.
exec_agent is the right default for text
For anything where the output is mostly prose, exec_agent is usually the cleanest choice.
The docs are blunt: exec_agent loads the project agent prompt, sends spec.prompt, and returns plain text. One call. No tool loop. No hidden side effects.
That is exactly what I want for recurring writing jobs.
If I need a weekly Dev.to draft, a release note, a short status summary, or a Telegram blurb, I do not need the routine to browse the filesystem, call MCPs, and negotiate with itself. I need it to write a paragraph that I can inspect.
A text-only routine is easier to test too. If the output is off, I know the problem is in the prompt or the source input. I do not have to ask whether a tool call changed state before the model finished its thought.
super_agent is for orchestration, not simple prose
super_agent has its place. It gets the full tool registry. It can write files, call MCPs, create tasks, and do real multi-step work.
That power is useful when the routine must coordinate several actions that are not just text generation. If I want APX to inspect state, make a decision, and then act on it, super_agent is the right kind.
But I do not want to pay that complexity tax unless I need it.
The more the routine can do, the more ways it can surprise me. That is fine for an orchestration job. It is bad for a weekly post where I already know the shape of the result.
This is where I keep reminding myself of a rule that sounds simple but saves time: if the job is "write something," do not start with the strongest tool. Start with the smallest one that can only write.
The delivery step should stay outside the model
The cleanest pattern I found is:
-
exec_agentwrites the text -
post_commandshandle delivery - shell publishes or sends the result
That separation keeps side effects visible.
If the publish step fails, I know where it failed. If the text is bad, I know it was the model output. If the delivery script is wrong, I know the model is not the bug.
APX documents this pipeline clearly. pre_commands can generate input for the model. post_commands can use $APX_LLM_OUTPUT, $APX_PRE_OUTPUT, and $APX_STATUS. That is a good shape because it makes the handoff explicit instead of magical.
It also fits how I actually want to use the machine.
For my weekly personal Dev.to post, I want a repeatable flow:
- collect notes
- draft article text
- validate JSON
- publish to Dev.to
- post a short Bluesky link
That is not one problem. That is five small problems.
The model should only own the parts where language helps.
The double-send gotcha taught me the rule
I hit one failure mode that made this lesson stick.
If a super_agent routine also has a post_commands step that calls apx telegram send, you can end up with two messages: one from the agent's own send_telegram tool call, and one from the shell step.
APX does have a suppression heuristic for that case, but the clean fix is still the same: do not make the model own delivery when the shell can do it directly.
That is the kind of bug I do not want to rediscover every week. It is not a hard algorithmic problem. It is a boundary problem.
Once I split text from delivery, the routine became much easier to reason about:
- the model can be wrong without side effects
- the shell can fail without corrupting the draft
- the final delivery step is obvious in logs
That is boring. That is also good.
Why boring beats clever in automation
I used to think a clever routine was the goal.
Now I think a routine that is easy to explain is the goal.
Boring automation is a feature because it makes retry behavior sane. It makes the output inspectable. It makes future changes local. Most important, it keeps the runtime honest about what belongs in the repo and what belongs on the machine.
That is the APC/APX shape again:
- APC keeps the durable project contract
- APX keeps local runtime state and execution
- routines turn that split into something scheduled and repeatable
If I let the model blur those lines, I usually create work for myself later. If I keep the boundaries tight, I get a system I can actually maintain.
That is why my favorite APX routine is now the least exciting one: exec_agent writes the text, shell ships it, and the model never has to pretend it owns the whole pipeline.
A practical rule I follow now
When I design a routine, I ask three questions:
- Does this step only need prose? Use
exec_agent. - Does this step need tool use or multi-step coordination? Use
super_agent. - Does this step only need a deterministic side effect? Keep it in shell.
That rule has made APX feel more reliable and less theatrical.
The point of the system is not to make every job feel autonomous. The point is to make the right parts of the job autonomous and leave the rest obvious.
That is the version I trust.
Top comments (1)
Boring routines are underrated in agent systems. The more autonomy a workflow gets, the more valuable predictable rails become: same inputs, same checks, same receipt, and no clever shortcut when the boring path is safer.