HostFileBackend

org.llm4s.agent.harness.HostFileBackend
See theHostFileBackend companion object
@Experimental
final class HostFileBackend extends FileBackend, AutoCloseable

Files in a directory of the host, opt-in: the agent sees root as / (design ยง4.40).

'''One writer per root.''' HostFileBackend.open takes an exclusive OS lock on the root, held until close: a second open of the same root, or of one containing it or inside it, from this JVM or another process, is refused with HostRootInUse. The lock excludes other backends that share the lock directory, not an editor or a build writing the directory; a replay still sees such a change, by its digests.

'''One path, one file.''' A path the model sees is the only way to reach its file: symbolic links, files with other hard links, other file systems mounted inside the root, names ending in a dot or a space and the backend's own temporary names are refused and never listed. open refuses a root whose file system makes 8.3 short names, and one it cannot check (no hard-link counts, as on NTFS, or a root it cannot probe) unless allowUncheckedIdentity.

'''Not a security boundary.''' It is a convenience for a trusted directory. Never give it a root an untrusted process can write, a sandboxed command included: such a process can swap a directory for a link between the backend's check and its open. Untrusted commands belong in the workspace sandbox (F2).

  • '''Replay.''' Its target is Verifiable (C4): each mutation runs through ReplayGuard.verifiable under ctx.effectKey("file.write" | "file.edit" | "file.delete", path), and its effect is a compare-and-set under the backend's own effect lock, so a run that lost its claim cannot overwrite its successor's change.
  • '''Atomic.''' A write or edit moves a flushed temporary file over its target, keeping the target's POSIX permissions, owner, group, ACL and DOS flags, or is refused where it cannot keep them.
  • '''Order.''' Every mutation locks files/host/<root>: a batch's mutations through one backend apply one at a time, in call order.
  • '''Bounds.''' A file over maxFileBytes cannot be read, written or edited, and search skips it. ls, find and search read at most maxScanEntries directory entries, never a stream past it, and report a cut scan as truncated: a directory over the bound lists as empty, and a walk stops at the first directory it cannot read whole, so a cut result is a prefix of the full one.
  • '''Tenant.''' Not scoped: one root is one tenant's. Build one per tenant with FileBackend.resolved.
  • '''Snapshot.''' Reads see the file as it is now, not as the batch's thread state.

Attributes

Companion
object
Graph
Supertypes
trait AutoCloseable
trait FileBackend
class Object
trait Matchable
class Any

Members list

Value members

Concrete methods

def close(): Unit

Closes the backend; idempotent, and never blocks. Every operation that starts after it is refused. One already running finishes as it would have, and the root's lock is released when the last of them ends (at once if none is running), so no other backend can open the root while this one still writes to it. Safe to call from inside one of this backend's operations.

Closes the backend; idempotent, and never blocks. Every operation that starts after it is refused. One already running finishes as it would have, and the root's lock is released when the last of them ends (at once if none is running), so no other backend can open the root while this one still writes to it. Safe to call from inside one of this backend's operations.

Attributes

Deletes the file, or the empty directory, path.

Deletes the file, or the empty directory, path.

Attributes

def edit(path: VPath, old: String, replacement: String, replaceAll: Boolean, ctx: FileContext): Result[(FileEdited, StateUpdate)]

Replaces old with replacement in the file path: the one occurrence, or every occurrence when replaceAll. Refused when old is empty or absent, or occurs more than once and not replaceAll.

Replaces old with replacement in the file path: the one occurrence, or every occurrence when replaceAll. Refused when old is empty or absent, or occurs more than once and not replaceAll.

Attributes

def find(under: VPath, accept: VPath => Boolean, limit: Int, ctx: FileContext): Result[Listing[VPath]]

The files within under that accept takes, in path order, at most limit: accept is applied before the limit, so a file it refuses never takes a place (a composite passes a predicate that refuses the paths a mount shadows, and relies on this to fill its limit with visible files). search likewise.

The files within under that accept takes, in path order, at most limit: accept is applied before the limit, so a file it refuses never takes a place (a composite passes a predicate that refuses the paths a mount shadows, and relies on this to fill its limit with visible files). search likewise.

Attributes

def locks(path: VPath): Set[LockKey]

One key for every path: a backend's mutations apply one at a time, so two names a case-insensitive or normalising file system takes for one file are ordered whatever they are.

One key for every path: a backend's mutations apply one at a time, so two names a case-insensitive or normalising file system takes for one file are ordered whatever they are.

Attributes

def ls(path: VPath, limit: Int, ctx: FileContext): Result[Listing[FileInfo]]

The entries of the directory path, in name order, at most limit. A directory with more than maxScanEntries entries lists as empty and truncated: the backend never reads past its bound, and any part of such a directory would depend on the file system's order.

The entries of the directory path, in name order, at most limit. A directory with more than maxScanEntries entries lists as empty and truncated: the backend never reads past its bound, and any part of such a directory would depend on the file system's order.

Attributes

def read(path: VPath, offset: Int, limit: Int, ctx: FileContext): Result[FileLines]

At most limit lines of the file path, from the 0-based line offset.

At most limit lines of the file path, from the 0-based line offset.

Attributes

def search(under: VPath, regex: Pattern, accept: VPath => Boolean, limit: Int, ctx: FileContext): Result[Listing[GrepMatch]]

The lines matching regex of the files within under that accept takes, in path then line order. regex is the model's: match it with org.llm4s.security.RegexSafetyManager.safeFind, never a bare matcher, so a catastrophic pattern is bounded and cannot overflow the stack.

The lines matching regex of the files within under that accept takes, in path then line order. regex is the model's: match it with org.llm4s.security.RegexSafetyManager.safeFind, never a bare matcher, so a catastrophic pattern is bounded and cannot overflow the stack.

Attributes

def write(path: VPath, content: String, ctx: FileContext): Result[(FileWritten, StateUpdate)]

Writes the file path, creating it or replacing it.

Writes the file path, creating it or replacing it.

Attributes

Inherited methods

final def glob(pattern: Glob, under: VPath, limit: Int, ctx: FileContext): Result[Listing[VPath]]

The files within under whose path relative to under matches pattern.

The files within under whose path relative to under matches pattern.

Attributes

Inherited from:
FileBackend
final def grep(regex: String, under: VPath, include: Option[Glob], limit: Int, ctx: FileContext): Result[Listing[GrepMatch]]

The lines matching regex in the files within under. include filters the files: a pattern with no / matches a file's name, one with a / its path relative to under (as ripgrep's --glob).

The lines matching regex in the files within under. include filters the files: a pattern with no / matches a file's name, one with a / its path relative to under (as ripgrep's --glob).

Attributes

Inherited from:
FileBackend

Concrete fields

val maxFileBytes: Long
val maxScanEntries: Int

C4's class for this target.

C4's class for this target.

Attributes

val root: Path
val writes: Set[StateKey[_, _]]

The state keys the StateUpdates of this backend's mutations touch; a tool declares them in writes.

The state keys the StateUpdates of this backend's mutations touch; a tool declares them in writes.

Attributes