Skip to content

Releases: Galfurian/filo

filo 0.4.3

Choose a tag to compare

@github-actions github-actions released this 15 Oct 15:44
a4481e6

An agent that runs its commands in a git worktree of the project can now answer. If you use filo with Codex or agy in worktrees, upgrade: until then such an agent was told that a recipient who was connected was not.

Fixes

  • A registered session kept looking at the scope of the directory it ran in. A scope is the directory a command runs in, and an agent does not always run its commands where its scope is. A Codex session registered in a project, whose commands ran in a worktree of it (.worktrees/NAME), looked at the scope of the worktree, found nobody (filo who said none) and filo send reported recipient 'manager' is not connected, while the manager, who wrote to the project, always reached it. Only --scope made it work, and only an agent that had been told to say it. This is how scopes have been chosen since the first version.

    Now who, send, status, show, leave and rename work in the scope where the session that runs them is registered, whichever directory its shell is in. The session is the one of the client that runs the command ($CODEX_THREAD_ID, $ANTIGRAVITY_CONVERSATION_ID, or the id variable of an adapter), and it is used when the current directory is another scope and holds no registration of it. send also uses the one scope where its --from name is a member, which covers a listener.

Changes in behaviour

  • A command that works in another scope than its directory says so, on stderr: filo: working in X, not in Y: this session is registered there (--scope chooses another). who names the scope it looked at, in the text and in the JSON.
  • It is never a guess: when two scopes hold the session, or the name, nothing is inferred. --scope always wins. join, register and watch keep using the directory: they create the membership or read what a listener here keeps.
  • A broken adapters file no longer stops a command that does not need it (send with a name that is a member of a scope, for example).

Upgrade

filo upgrade, or the one-line install again. A session that was started before the upgrade runs the old version until it is restarted; an agent in a worktree can use --scope /path/of/the/project until then.

Security posture

Unchanged from 0.4.2: get.sh is signed with Sigstore, covered by the build provenance and listed in SHA256SUMS, like the archive.

filo 0.4.2

Choose a tag to compare

@github-actions github-actions released this 14 Oct 20:48
8b82ade

Three fixes from a second review. The first matters if you start several sessions at once: two of them could end up with the same name, and one could no longer be reached.

Fixes

  • Listeners that joined at the same moment could get the same name. join looked for a name in use, removed the files of that name and bound a socket in three steps, and the metadata that says a name is in use is written only later. The second listener saw nothing, removed the socket that the first had just bound and bound its own: both were claude, the first could not be reached, and what was sent to claude went to the other. Eight join --as same started together gave three different names, some of them twice. A name is now held with a lock (NAME.lock) for as long as its listener lives, a detached one included; the system takes it back when the process ends, however it ends. The checks that existed (a registration of that name, a listener of an older filo) stay.
  • A client that sent a byte now and then could hold a listener for as long as it liked. The timeout of a read started again with every byte, and a listener serves one connection at a time: while it was held nothing else was served, the spool was not delivered and the listener did not notice that its parent had ended. The whole message now has to arrive within the time, for the listener and for a client waiting for an answer. (It takes a process of your own user, which SECURITY.md puts out of scope; this is robustness.)
  • filo upgrade judged an address by how it began. http://127.0.0.1:80@example.org/ began like the local address that is allowed for a mirror and the tests, and is example.org. The address is now parsed: https, or http to 127.0.0.1 or localhost on a port, and never one that names a user. upgrade still insists on a get.sh stamped for the release, shows its dry run and asks before it runs it.

Changes in behaviour

None that you would notice. There is one more small file for each name that has had a listener, NAME.lock, in the runtime directory (0600), like the other state; it does not need to be cleaned.

Upgrade

filo upgrade, or the one-line install again. A session that was started before the upgrade runs the old version until it is restarted.

Security posture

Unchanged: get.sh is signed with Sigstore, covered by the build provenance and listed in SHA256SUMS, like the archive.

filo 0.4.1

Choose a tag to compare

@github-actions github-actions released this 14 Oct 18:47
6b6ae77

Fixes for nine problems found in a review of 0.4.0, two of which lost a message that its sender had been told was delivered. If you use filo with a detached listener (the Claude setup), upgrade.

Fixes

  • A big message with non-ASCII text could be reported as delivered and then be lost. A listener keeps what it receives in a file, and a sender leaves one in the spool when the recipient is away, with every non-ASCII character escaped, which takes up to six times the size of the message; a file above the size of one frame was refused. A message of 3 MB of Chinese text was "sent directly" and then filo show could not find it and filo watch never printed it; one that waited in the spool was deleted when its recipient came back. A file may now hold what a frame holds, escaped.
  • filo send refuses a message that is too big to carry (about 4 MB of text) and says so, instead of falling back to a spool that would have thrown it away.
  • filo watch skipped a message whose id was older than the last one it printed. An id is made by the sender before it delivers, so a slow sender, or the spool (every message from a sandboxed sender goes through it), could deliver a message after one sent later. It was saved and never printed. The cursor now says when a message was received, not which id it had; the cursor of an older filo is understood.
  • A bad adapters file stopped filo who, filo join and a message to a listener that was away, for everyone, even with nobody registered, and join left its socket behind. The error is printed to stderr and the command goes on.
  • A name of 300 characters, a directory that had been removed, or a --scope too long to look up ended in a traceback. Names are limited to 64 characters, and any remaining file system error is a one-line message with exit status 1.
  • filo join --parent 0 made a listener that never ended, because kill(0, 0) always succeeds. --parent must be above 0.
  • The listener wrote an acknowledgement file named after the id that a raw client of its socket sent, so ../../x wrote outside its directory and a long id made the answer an error after the message had been shown. It now does so only for an id that filo could have made. (It needs a process of your own user, which SECURITY.md puts out of scope; this is robustness.)
  • A spooled message was deleted before it was delivered, so a listener that ended in between lost it. It is deleted after, so one that is cut short is delivered again (a message may repeat; one is never lost). A message that cannot be shown at all is still dropped, or the listener would fail on it at every start.
  • A delivery command was given every variable in the registration file, not only the ones its adapter captures. It now gets only those (defence in depth: it takes a process of your own user to write the file).

Also

  • Old acknowledgements are pruned once a minute, with the inbox, not twice a second by every listener.

Changes in behaviour

A name longer than 64 characters, a message of more than about 4 MB of text and a --parent of 0 or less are now errors; they did not work before, and failed late or not at all. Nothing else a user does changes.

Upgrade

filo upgrade, or the one-line install again. A session that was started before the upgrade runs the old version until it is restarted.

Security posture

Unchanged: get.sh is signed with Sigstore, covered by the build provenance and listed in SHA256SUMS, like the archive.

filo 0.4.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 17:39
af9a420

A Claude session that leaves and rejoins no longer wakes everyone else, and a message says which scope it comes from correctly. If you use Claude with filo, read the first section: a session that is already running keeps the old behaviour until it runs /filo again.

Changes in behaviour

  • The listener of a Claude session is no longer its Monitor. A Claude Monitor ends after at most 30 minutes. While the listener was the Monitor, each end told every other session that it left, and each restart that it joined, and every one of those notices woke them: one session's Monitor cost a turn to all the others, twice every half hour. The skill now starts filo join --as NAME --detach --parent "$CLAUDE_PID", a listener in a process of its own that ends with the Claude session, and a Monitor on filo watch --as NAME --preview 400, which prints one JSON line per message and nothing else. Starting the Monitor again changes nothing for anyone and prints what came in meanwhile. A session started before the upgrade keeps its Monitor on join until it runs /filo again.
  • A message now says which scope its sender belongs to, not the directory it happens to run in. An agent registered in a project that moved to a git worktree used to be reported as a stranger, with a reply command that failed. A message event has "foreign": true and origin (the sender's scope) only when the message comes from another scope, and its reply command then carries --scope; the scope field is gone from events, and an ordinary event is about 150 characters shorter. Keyword scopes work too. Codex, agy and OpenCode are told "sent from SCOPE (another scope)" in the same case.
  • filo leave --name NAME stops a listener (it was an error before).
  • A message sent to a detached listener counts as delivered when the listener has it, whether or not a watch is running.
  • The skill no longer tells an agent to stop at a message from another scope and wait for you. It says who wrote and from where when it reports, and then decides as for any message, asking you first only when the message asks it to change files, run commands, send something elsewhere or do something outside its task. Nothing in a message is from the user, as before.

New

  • filo join --detach --parent PID: a listener that goes on by itself, in a session of its own, and ends with PID or with filo leave --name NAME. It is not a service: no port, its own user only, and only when a session started it.
  • filo watch --as NAME [--preview N]: the messages of a detached listener, one JSON line each, until it ends ({"kind":"ended"}). A message printed just as the previous watch ended can come again with the same id: filo may repeat one and never loses one.
  • The README is ordered by use: quick start, profiles, commands, install and troubleshooting first; how it works, the clients one by one and the rest after, under a note that says the later sections are not needed to use filo.

Upgrade

filo upgrade, or the one-line install again. A session that was started before the upgrade runs the old version until it is restarted.

Security posture

Unchanged from 0.3.1: get.sh is signed with Sigstore, covered by the build provenance and listed in SHA256SUMS, like the archive. SECURITY.md now also describes what a detached listener is and is not, and says that home and origin are what the sender's process reports, as --from is.

filo 0.3.1

Choose a tag to compare

@github-actions github-actions released this 14 Oct 14:05
d1d78a8

Fixes for eight problems that property-based tests found in how filo reads what other processes write to it. If you run filo, upgrade: one of them let a single frame sent to a listener end that listener.

Fixes

  • A deeply nested JSON frame ended the listener. A document nested tens of thousands of levels deep (a 100 KB frame is enough) raised RecursionError in the reader of a socket frame and of a metadata file, and nothing caught it. Any process of yours could stop a listener that way (the sockets and files are private to your user, so no one else could). It is now a protocol error, like any other bad frame.
  • Infinity in a number field raised an exception that the callers did not catch. It is now an ordinary bad value.
  • A name could be . or .., which as a directory under spool/ and inbox/ is the directory above it. Both are refused now.
  • A message whose origin held a NUL character was lost instead of delivered. An origin that cannot be checked now counts as coming from another directory.
  • The reply command put the sender in unquoted, so a sender with a space or a semicolon in its name split into several words. It is quoted now; valid names are written as before.
  • A message that contained {scope}, {session} or {message} was altered on its way to a client run by an adapter, which replaced the placeholders one after the other. They are replaced in a single pass, and the message arrives as it was written.
  • A shortened event of an empty body was marked truncated, with nothing cut.
  • --scope ~nobody, for a user that does not exist, ended with a traceback. It is a keyword now, like any other word.

Changes in behaviour

. and .. are no longer valid names (filo join --as .. is an error). Nothing else a user does changes.

Tests

tests/test_properties.py states what must hold for any input, with Hypothesis (a dev tool only: filo still has no dependency). Each of the eight is now an @example of the test that found it. HYPOTHESIS_PROFILE=explore searches harder; CONTRIBUTING.md says how.

Upgrade

filo upgrade, or the one-line install again. A session that was started before the upgrade runs the old version until it is restarted.

filo 0.3.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 13:23
7ddbf2f

A message can no longer be lost to a shortened notification, and a message now says where it came from. If you use Claude, read the first section: a running Monitor has to be started again to get the fix.

Changes in behaviour

  • The Monitor of a Claude session starts with --preview 400. A Claude Monitor shows only the start of a long line (about 500 characters) and cuts it without a mark, so the end of a message, often the instruction, could be lost without the reader knowing. The skill now starts it as filo join --as NAME --json --preview 400, and an agent that sees "truncated": true reads the whole message with filo show ID. A Monitor that is already running keeps the old behaviour until the session runs /filo again.
  • In every message event the body comes last, after from, id and reply, so a client that cuts the end of a line still shows who wrote and how to answer. Only the order of the keys changed.
  • A message says where it was sent from. The event has the scope of the listener, the origin (the directory the sender ran in) and, when that is another directory, "foreign": true; its reply command then carries --scope with that directory. For Codex, agy and OpenCode the text says "sent from DIRECTORY (another scope)". The skill tells an agent to pass a foreign message on to you and wait. origin is what the sender's process reports, as --from is: it lets an honest agent notice that a message crossed scopes and protects against nothing else.
  • The participants event of join --json lists registered sessions too (registered). It was [] when only a Codex or agy session was there.

New

  • filo show ID prints the whole text of a message you received (--json for the record). Each listener keeps what it receives, privately, for 24 hours.
  • filo join --json --preview N cuts an event longer than N characters (at least 100) and says so: "truncated": true, the length of the whole message and show, the command that prints it. Without it an event is whole, which the OpenCode plugin needs.
  • filo who --all [--json] lists every scope that has someone in it. A scope shows up once a session has joined or registered with this version.

Upgrade

filo upgrade, or the one-line install again. A session that was started before the upgrade runs the old version until it is restarted.

Security posture

Unchanged from 0.2.0: get.sh is signed with Sigstore, covered by the build provenance and listed in SHA256SUMS, like the archive. SECURITY.md now also describes what a listener keeps and what origin is worth.

filo 0.2.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 12:29
d899c9c

Install and upgrade filo without a checkout. This release is the first one that carries get.sh, so the one-line install works from here on.

New

  • One-line install and uninstall. get.sh, attached to each release, downloads the release archive over https, checks its SHA-256, unpacks it into ~/.local/share/filo (FILO_HOME changes that) and runs the install.sh inside, which says which links it makes before it makes them.

    curl -fsSL https://github.com/Galfurian/filo/releases/latest/download/get.sh | sh
    curl -fsSL https://github.com/Galfurian/filo/releases/latest/download/get.sh | sh -s -- uninstall

    Both take --dry-run (sh -s -- --dry-run, or download get.sh and run sh get.sh --dry-run). Uninstall deletes only a directory that holds the marker get.sh wrote, and refuses /, your home directory, a relative path and a link. ./install.sh from a checkout works as before.

  • filo upgrade. filo upgrade --check says whether a newer release exists, by looking where /releases/latest points. filo upgrade fetches the get.sh of that release, refuses it unless it is stamped for that version, shows its dry run, asks (or takes --yes) and runs it for the directory this filo runs from. A checkout is told to git pull; a copy that did not come from get.sh is refused.

  • An opt-in notice of a new release. With FILO_CHECK_UPDATES=1, filo who looks at most once a day, for two seconds at most, and adds one line on stderr when a newer release exists. It is off unless you set it, silent when offline and never printed with --json. The skill tells agents to pass the notice on to you and not to run filo upgrade themselves.

Behaviour to know about

filo upgrade and the optional notice are the only places where filo uses the network; messages never leave the machine. FILO_RELEASES_URL accepts https only. SECURITY.md has a section on this.

Security posture

get.sh is stamped by the release workflow with the version and the SHA-256 of the archive, so a copy from a release can only fetch that archive; an unstamped copy refuses to run. It is signed with Sigstore (get.sh.sigstore.json), covered by the build provenance and listed in SHA256SUMS, like the archive. The hash and the archive come from the same release, so someone who can change a release can change both: check get.sh with the signature or the provenance first if that matters to you (SECURITY.md, "Checking a release").

The project is registered for the OpenSSF Best Practices badge, shown in the README.

filo 0.1.1

Choose a tag to compare

@github-actions github-actions released this 13 Oct 19:21
ba8e839

Fixes that came out of an audit of 0.1.0, one of them a change in behaviour for Codex. If you use Codex with filo, read the first section.

Changes in behaviour

  • filo no longer guesses which Codex session you mean. Before, send --to codex with nobody registered, and register without a thread id, took the newest thread of the directory from Codex's own database. Both are gone. A message goes only to a session that registered itself: run /filo in Codex, and register needs $CODEX_THREAD_ID or --session. Sending to an unregistered codex is now an error that says so.
  • A registration is forgotten only when none of the client's processes run any more. It used to keep the newest ones when fewer processes ran than there were registrations, which could keep the dead session and drop the live one. When it cannot tell which one ended it now keeps them all.
  • filo refuses a runtime directory it should not trust: a link, a directory that is not yours, or one that is open to others and already holds a link or something of another user's. The same checks apply to the directory of every scope, and metadata files are read without following links.

Fixes

  • Two sessions registering at the same moment could end with the same name. Eight simultaneous registrations lost or doubled a name in 18 runs out of 40; now it is 0 out of 40. A name is claimed by creating its file with a link, which only one process can do. rename claims its new name the same way.

New

  • filo --version.
  • CONTRIBUTING.md, a Code of Conduct, bug and feature forms and a pull request template.
  • CI also checks the workflows with actionlint and zizmor.

Security posture

main is protected by a ruleset that applies to everyone: a pull request, five passing checks (Linux 3.10 and 3.12, macOS and CodeQL for Python and JavaScript), a linear history. Private vulnerability reporting is on; see SECURITY.md.

The archive is signed with Sigstore and has a build provenance attestation, as for 0.1.0.

filo 0.1.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 18:53

First release of filo: direct messaging between interactive AI coding sessions in the same directory, without taking the sessions over.

What it does

  • Connects live sessions of Claude Code, Codex, Antigravity (agy) and OpenCode. Each stays an ordinary interactive session; you keep typing to it while it works with the others.
  • Sessions message each other by name (filo send --to librarian ...), and you start each one with a role: /filo manager, /filo worker, /filo researcher, /filo librarian, or your own profile in ~/.config/filo/profiles.
  • A session that is away gets its messages when it rejoins; one that runs filo leave is forgotten.
  • Any other client can be added from a JSON file, with no code change.

How a message gets in

  • Claude: a Monitor on filo join. Codex: codex queue. agy: agentapi send-message, using the session's own address and token. OpenCode: a plugin that starts filo join and turns each message into a prompt in the same session.

Install and remove

./install.sh --dry-run prints exactly what it will do; ./install.sh only makes symbolic links, needs no sudo and changes no permissions. ./uninstall.sh removes only those links.

Systems

Used day to day on Linux (Ubuntu 24.04 under WSL2). The tests also run on macOS in CI; the client integrations have not been tried on a Mac. Windows needs WSL.

Security

One user on one machine; not a network service. The runtime directory is private to the user and filo refuses one that is not. A message is untrusted input to a model, and --from is a label, not an identity. The agy and OpenCode integrations use interfaces that are not a stable public contract, so a client update can break them. The full model is in SECURITY.md. The archive is signed with Sigstore and has a build provenance attestation.