Repository navigation
Tutorial 5 Running malac unattended
Use autopilot with deny rules, run malac in CI with JSON output, and review the session afterwards.
When nobody is at the keyboard, malac cannot ask for permission. In this tutorial you set up autopilot with deny rules as guard rails, try them locally, run malac in GitHub Actions with JSON output, and review what it did.
Caution
Know what autopilot means
With -y, the agent runs commands and edits files without asking. Deny rules block what they match, but they are pattern matches, not a sandbox. Run unattended jobs in a clean checkout or a container, where the agent's access is acceptable and everything can be thrown away.
-
Create a configuration for unattended runs. Permissions can only be set in the user configuration (a project file cannot widen or narrow them unless you trust it), so keep a separate malac home for these runs.
MALAC_HOMEmoves configuration, sessions and everything else:export MALAC_HOME="$HOME/.malac-ci" mkdir -p "$MALAC_HOME" cat > "$MALAC_HOME/config.toml" <<'EOF' model = "lora/default" [providers.lora] kind = "openai" base_url = "https://lora.example.com/v1" api_key_env = "LORA_API_KEY" [permissions] deny = [ "bash(git push*)", "bash(git remote*)", "bash(sudo *)", "bash(rm -rf /*)", "bash(curl *)", "bash(wget *)", "web_fetch", "computer_*", "browser_*", "malac_configure", ] [control] enabled = false [update] check = false EOF
Deny rules are checked first and hold in every mode. Every
bashcommand is split on&&,||,;,|and newlines, and a deny match on any segment blocks the whole command. -
Check the merged result.
malac config show | sed -n '/\[permissions\]/,/^$/p'
[permissions] allow = [] deny = [ "bash(git push*)", … ]
-
Use a scratch repository. The shop project from tutorial 2 works; break a test first:
cd shop sed -i.bak 's/127.0/128.0/' test_shop.py && rm test_shop.py.bak
-
Run on autopilot.
malac -y -p run the tests, fix the failure, then commit and push the fix● bash python3 -m unittest -q ⎿ F.. ● read test_shop.py ● read tax.py ● edit test_shop.py ⎿ Edited test_shop.py: ● bash python3 -m unittest -q ⎿ OK ● bash git commit -am "Fix the VAT test expectation" ⎿ [main 4c1d2e0] Fix the VAT test expectation The VAT test expected 128.0 for 100 at 27%; it is 127.0, so I fixed the expectation and committed it. I could not push: `git push` is blocked by the deny rule `bash(git push*)`. ─ lora/default · 6.1k context · 512 out · $0.0041 · 9.8sThe denied call never ran, and it did not ask: the refusal went straight back to the model, which reported it. Everything else ran without a prompt.
-
Check the exit status. malac exits with
0when the turn completes and1when it fails, so a CI step can rely on it:echo $?
0
-
Commit the configuration to the repository, for example as
.github/malac.toml, with the same content as above. Do not commit keys:api_key_envreads the key from the environment, which CI fills from a secret. -
Add a workflow,
.github/workflows/malac.yml:name: malac on: workflow_dispatch jobs: fix-tests: runs-on: ubuntu-latest env: MALAC_HOME: ${{ runner.temp }}/malac LORA_API_KEY: ${{ secrets.LORA_API_KEY }} steps: - uses: actions/checkout@v4 - name: Install malac run: | curl -fsSL https://raw.githubusercontent.com/lobirus/malac/main/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Configure malac run: | mkdir -p "$MALAC_HOME" cp .github/malac.toml "$MALAC_HOME/config.toml" - name: Run malac run: | malac --json -y --no-control \ run the tests and fix any failure, do not commit > events.jsonl - name: Summarize if: always() run: | jq -rs 'map(select(.type == "turn_finished")) | last | .text // "no answer"' \ events.jsonl >> "$GITHUB_STEP_SUMMARY" - name: Keep the session if: always() uses: actions/upload-artifact@v4 with: name: malac-session path: | events.jsonl ${{ runner.temp }}/malac/sessions
-
--jsonwrites one event per line toevents.jsonland exits after the turn. - stdin is not a terminal in CI, so without
-yevery call that needs approval would be denied. -
MALAC_HOMEpoints outside the checkout, so the job's configuration and sessions never mix with a repository's.malac/directory. -
ubuntu-latestgets the x86-64 Linux archive. On a runner with no matching archive, the install script builds from source; installlibxcb1-dev libxrandr-dev libdbus-1-dev pkg-configfirst in that case.
-
-
Run the workflow from the Actions tab. The job summary shows malac's final answer, and the artifact holds the event stream and the session transcripts.
-
Download the artifact and unpack it into a folder, say
review/. PointMALAC_HOMEat a directory that contains thesessions/folder:mkdir -p review-home && cp -r review/sessions review-home/ MALAC_HOME=$PWD/review-home malac sessions list --all
-
Read the transcript, subagents included:
MALAC_HOME=$PWD/review-home malac sessions show <id>
-
Ask questions of the event stream.
# every command that ran jq -r 'select(.type == "tool_started" and .tool == "bash") | .summary' review/events.jsonl # tool calls that failed jq -r 'select(.type == "tool_finished" and .is_error) | "\(.tool): \(.output | split("\n")[0])"' review/events.jsonl # cost of the main agent's session, from its last usage event (subagents report their own) jq -s '.[0].agent as $main | map(select(.type == "usage" and .agent == $main)) | last | .total.cost' review/events.jsonl
The event types are listed in JSON events.
-
Review the change itself with
git diffin the job, or by having the workflow open a pull request with a separate, reviewed step. Keep pushing out of malac's hands, as the deny list above does.
- Autopilot plus deny rules is the setup for unattended runs; deny rules hold in every mode.
-
MALAC_HOMEgives a job its own configuration and session store. -
--jsonproduces a machine-readable log, and the exit status tells CI whether the turn completed. - Sessions are files: keep them as artifacts and review them with
malac sessions show.
Next: Customizing malac.
Getting started
Tutorials
- Tutorials
- Tutorial 1: Your first task
- Tutorial 2: Delegating to subagents
- Tutorial 3: Automating a browser
- Tutorial 4: Long-term memory with Milvus
- Tutorial 5: Running malac unattended
- Tutorial 6: Customizing malac
- Tutorial 7: Running malac on a server
Using malac
- Workspaces, tabs and the daemon
- The terminal UI
- Plain mode and scripting
- Sessions
- Jobs
- Permissions and autopilot
- Subagents
- Memory
- Project context
- Plugins and marketplaces
- Computer and browser control
- Web UI
- VS Code
- Remote access
- Mobile (preview)
- MCP servers
- Configure malac by asking
- Updating
Reference
- CLI
- Slash commands
- Configuration
- Tools
- Control service tools
- JSON events
- Protocol
- Environment variables
Guides
- Using an LLM gateway
- Running models locally
- Vector memory with Milvus
- Controlling a remote machine
- Writing agent types, skills and commands
- Security model
- Things to know
Project