Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}", "examples/**/*.exs"]
]
63 changes: 63 additions & 0 deletions examples/agent_session.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Stateful agent sessions on the just_bash + vfs + exgit stack.
#
# A session is a value. Its filesystem is a mount table: an in-memory
# backend at "/", real GitHub repositories (cloned in pure Elixir —
# no git binary, no disk, no container) mounted under /repos. Drive
# it with bash strings; thread the returned value forward. Forking a
# session is variable binding; rolling back is using the old binding.
#
# No helpers — every line is what a real caller writes. Each exec
# crash-asserts its exit code, so this deterministic transcript
# doubles as a smoke test of the whole stack.
#
# elixir examples/agent_session.exs
#
Mix.install([
{:just_bash, github: "elixir-ai-tools/just_bash"},
{:exgit, "~> 0.1.0"}
])

repos = [
{"vfs", "https://github.com/ivarvong/vfs"},
{"exgit", "https://github.com/ivarvong/exgit"},
{"just_bash", "https://github.com/elixir-ai-tools/just_bash"},
{"pyex", "https://github.com/ivarvong/pyex"}
]

# Every VFS.Mountable implementation across whatever is mounted.
# /repos/*/lib globs across mount roots like ordinary directories.
grep = "grep -rln 'defimpl VFS.Mountable, for:' /repos/*/lib"

# Mount one repo per iteration; the same grep sees each addition.
session =
Enum.reduce(repos, JustBash.new(), fn {name, url}, session ->
IO.puts("\n== mount #{name}\n$ #{grep}")
{:ok, repo} = Exgit.clone(url)
session = JustBash.mount(session, "/repos/#{name}", Exgit.Workspace.open(repo))

{%{exit_code: 0} = result, session} = JustBash.exec(session, grep)
IO.write(result.stdout)
session
end)

# Writes persist across execs: one command's output is the next
# command's input, carried by the returned session value.
{%{exit_code: 0}, session} = JustBash.exec(session, "#{grep} > /work/matches.txt")
{%{exit_code: 0} = result, session} = JustBash.exec(session, "wc -l < /work/matches.txt")
IO.puts("\n== agent wrote /work/matches.txt: #{String.trim(result.stdout)} matches")

# Checkpoint and rollback: a session is a value, so checkpointing is
# a variable binding and rollback is using the old binding.
checkpoint = session

{%{exit_code: 0}, session} = JustBash.exec(session, "rm /work/matches.txt")
{%{exit_code: 1}, _} = JustBash.exec(session, "cat /work/matches.txt")
{%{exit_code: 0}, _} = JustBash.exec(checkpoint, "cat /work/matches.txt")
IO.puts("== rm'd in the live session; the checkpoint still has it")

# Umount: the same grep no longer sees pyex.
IO.puts("\n== umount pyex\n$ #{grep}")
session = JustBash.umount(session, "/repos/pyex")

{%{exit_code: 0} = result, _session} = JustBash.exec(session, grep)
IO.write(result.stdout)
Loading