Skip to main content

A local-first free-form task inbox daemon for concurrent coding agents, Git worktrees, and maintainer-style integration.

Project description

Alluvium

Alluvium is a small, local-first runner for coding agents.

You drop free-form folders or bare files into inbox/. Alluvium claims each item into a stable tasks/<task-id>/ artifact folder, runs a low number of workers optimistically in isolated Git worktrees/branches, records task-local artifacts under .agent/, stores authoritative task state in a local SQLite database, and serially integrates durable repository changes through temporary integration worktrees.

The design goal is boring robustness over elaborate multi-agent choreography: a filesystem inbox, a tiny state index, bounded worker concurrency, explicit artifacts, and Git as the durable integration boundary.

The CLI is alluvium. The PyPI distribution is alluvium-swarm because the bare alluvium package name is already taken.

Quick start

Install:

uv tool install alluvium-swarm

Create a workspace:

mkdir my-alluvium
cd my-alluvium
alluvium init

Run the local runner in the foreground:

alluvium serve

For supervised/background use, prefer systemd, launchd, Windows service wrappers, Docker, or your normal process supervisor. alluvium daemon remains as a compatibility convenience wrapper.

Drop work into the inbox:

echo "Please summarize this." > inbox/request.txt

Check progress:

alluvium status
find tasks -maxdepth 3 -type f | sort

Stop or reload:

alluvium reload        # reload config.toml
alluvium stop-daemon   # graceful stop

For debugging instead of background mode:

alluvium daemon --foreground

What gets created

my-alluvium/
  config.toml
  inbox/            # public drop zone: folders or bare files not yet claimed
  tasks/            # stable system-owned task folders; state lives in SQLite
    <task-id>/
      input/        # original producer files
      .agent/       # prompts, logs, outputs, repo metadata, results
  repo/             # durable Git repo: code, docs, knowledge, skills, tests, etc.
  worktrees/        # one Git worktree per active task
  logs/
  .alluvium/        # lock/pid files and alluvium.db SQLite state index

A bare file is wrapped into a task folder automatically:

inbox/report.pdf
  ↓
tasks/20260516T101530Z-report.pdf-c771aa/
  input/
    report.pdf
  .agent/

A dropped folder keeps its contents but receives a unique internal name:

inbox/acme-contract/
  contract.pdf
  request.md
  ↓
tasks/20260516T101530Z-acme-contract-a8f2c1/
  input/
    contract.pdf
    request.md
  .agent/

.agent/ is reserved runtime state at the task root. Producer files are always moved under input/, so a producer-supplied .agent/ is treated as ordinary untrusted input at input/.agent/ rather than trusted runtime state.

Lifecycle

inbox/
  ↓ claim into stable tasks/<task-id>/
SQLite state:
  queued → running → worker_done → integrating
                                  ├── done/noop
                                  ├── done/merged
                                  ├── needs_revision → same task/branch is re-run and amended
                                  └── needs_human

Workers may run concurrently, but the default is intentionally low. Integration into repo/main is serialized and tested in a temporary integration worktree before the base branch is advanced.

The task folder does not move when state changes. SQLite is authoritative for state; integration details are also stored in:

.agent/integration.json

Examples:

{"status": "noop", "has_repo_changes": false}
{"status": "merged", "has_repo_changes": true, "merge_commit": "..."}
{"status": "needs_revision", "reason": "integration tests failed"}

When revision is needed, the integrator writes:

.agent/revision_request.md

and marks the task needs_revision in SQLite. The coordinator re-runs a worker on the same task ID, branch, stable task folder, and worktree so it can amend the branch.

Configure the worker

The default worker command uses pi with gpt5/gpt-5.4:high:

[agent]
command = ["pi", "--model", "gpt5/gpt-5.4:high", "--no-session", "-p", "@{prompt_file}"]
timeout_seconds = 3600

Edit config.toml to use another coding agent:

[agent]
# Placeholders: {task_id}, {task_dir}, {agent_dir}, {worktree}, {branch}, {prompt_file}
command = ["your-coding-agent", "--cwd", "{worktree}", "--prompt-file", "{prompt_file}"]
timeout_seconds = 3600

Alluvium also passes environment variables to the worker:

ALLUVIUM_TASK_ID
ALLUVIUM_TASK_DIR
ALLUVIUM_AGENT_DIR
ALLUVIUM_WORKTREE
ALLUVIUM_BRANCH
ALLUVIUM_PROMPT_FILE

The generated prompt asks the worker to:

  • infer the task from the free-form folder,
  • write .agent/understanding.md, .agent/plan.md, .agent/result.md, and .agent/result.json,
  • put task artifacts under .agent/outputs/,
  • commit useful durable repo changes on the task branch,
  • request human help by writing .agent/needs_human.md,
  • address .agent/revision_request.md when returned by the integrator,
  • log external effects under .agent/effects/ledger.jsonl.

Deterministic smoke-test worker

[agent]
command = ["python", "-m", "alluvium.builtin_agent", "--task-dir", "{task_dir}", "--worktree", "{worktree}", "--prompt-file", "{prompt_file}"]
timeout_seconds = 3600

The built-in agent inventories inputs but does not reason or edit meaningfully. It is useful for tests and smoke checks.

Pi runs from the task worktree by default. The prompt file includes the absolute task folder path, so pi can write .agent/ outputs and edit the durable repo.

Useful commands

alluvium init [path]                 # initialize workspace
alluvium serve                       # foreground local runner
alluvium daemon                      # compatibility: start background runner
alluvium daemon --foreground         # compatibility foreground mode
alluvium reload                      # SIGHUP daemon to reload config.toml
alluvium stop-daemon                 # graceful stop
alluvium stop-daemon --force         # SIGKILL if graceful stop times out
alluvium status                      # JSON status
alluvium run-once --ignore-settle    # process current inbox once, useful for tests
alluvium integrate-once              # run integrator once
alluvium example-task                # create a sample inbox task

Daemon logs:

logs/daemon.log

Daemon pid/lock:

.alluvium/daemon.pid
.alluvium/daemon.lock

Inbox publishing safety

The daemon waits for inbox items to be unchanged for inbox_settle_seconds before claiming them. This makes drag-and-drop/manual copies less likely to be processed halfway through.

For producer programs, prefer atomic publish:

inbox/.some-task.tmp/
  files...
  ↓ rename when complete
inbox/some-task/

Bare files are supported too:

inbox/report.pdf

Durable repo conventions

Alluvium does not impose a special knowledge/proposal topology. The durable repo is just a Git repository. It may contain code, docs, knowledge, skills, tests, generated assets, or anything else your project needs.

Workers should make the branch correct as a whole. The integrator acts like a repo maintainer: it serially reviews, tests, merges, or sends the task back for amendment.

Put project-specific conventions in:

repo/AGENTS.md

Development

git clone https://github.com/spoj/alluvium
cd alluvium
uv sync --dev
uv run pytest
uv run alluvium --help

Build and publish:

uv build
UV_PUBLISH_TOKEN=... uv publish

systemd user service

Use foreground mode under systemd:

[Unit]
Description=Alluvium Daemon
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/me/my-alluvium
ExecStart=/home/me/.local/bin/alluvium serve --config /home/me/my-alluvium/config.toml
Restart=always
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=60

[Install]
WantedBy=default.target

Then:

systemctl --user daemon-reload
systemctl --user enable alluvium
systemctl --user start alluvium
journalctl --user -u alluvium -f

Shutdown behavior

alluvium stop-daemon sends SIGTERM. The daemon stops accepting new work immediately, wakes sleeping loops, waits up to shutdown_grace_seconds for active workers, and then cancels remaining worker supervisors. Worker subprocesses receive SIGTERM and then SIGKILL if they do not exit.

If you send kill <pid> manually, the same SIGTERM path is used. If it appears slow, it is usually waiting for active worker cleanup. Use:

alluvium stop-daemon --force

only when you are okay with interrupting active workers.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

alluvium_swarm-0.4.0.tar.gz (27.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

alluvium_swarm-0.4.0-py3-none-any.whl (31.8 kB view details)

Uploaded Python 3

File details

Details for the file alluvium_swarm-0.4.0.tar.gz.

File metadata

  • Download URL: alluvium_swarm-0.4.0.tar.gz
  • Upload date:
  • Size: 27.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for alluvium_swarm-0.4.0.tar.gz
Algorithm Hash digest
SHA256 539f648f1b748c253623af049a0d73700753ed0e0e0ed24bcd283134fc4a425c
MD5 41c30c95b85fb1d5742ac2eaaa0d7b37
BLAKE2b-256 f94d796bb8eeac2558840f8cf6d7822d63799766ac56d8b76f0004c1a75ab8a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for alluvium_swarm-0.4.0.tar.gz:

Publisher: publish.yml on spoj/alluvium

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file alluvium_swarm-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: alluvium_swarm-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 31.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for alluvium_swarm-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d1618012645b9fc1b2d7b8d6fa25e7768fb8702784861d7077866512e36a8127
MD5 411395e27ab51542f0202482f7645fa0
BLAKE2b-256 1b271a8504545465548c39ff2e2886e9ac36bae865e02299e6e4c3e64db09fbd

See more details on using hashes here.

Provenance

The following attestation bundles were made for alluvium_swarm-0.4.0-py3-none-any.whl:

Publisher: publish.yml on spoj/alluvium

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page