Agent fleets¶
Polyglav composes into fleets of single-purpose agents, each a full Polyglav process scoped to a directory. A documentation agent owns a folder of PDFs, a code agent stays inside one repository, and a web-research agent has no filesystem access. The zero-dependency core keeps each process small, so many can run on one machine, and thin process and permission boundaries keep them apart.
One agent = one process = one folder¶
An agent is polyglav serve pointed at a project directory:
polyglav serve --path docs --port 8781 &
polyglav serve --path src --port 8782 &
Each process reads its own .polyglav/config.json (provider, model, system prompt, tool permissions, plugins) and writes its own sessions under .polyglav/sessions/. Nothing is shared at runtime, so a crash in one agent cannot take down the others and configuration drift stays isolated per agent.
Responsibilities and permissions¶
An agent's responsibility comes from its config, namely which tools are registered, and its permission is enforced by the worktree and tool policy, not convention.
- Worktree scoping: the tool policy treats the launch directory (
--path, or the current directory) as the worktree.file_read/list_dir/file_write/glob/grepon a path outside it escalate fromallowtoask(tools/policy.py:35). A doc agent cannot touch files in another agent's folder unless its config says so. - Headless auto-deny: in
serve/runmode,ask-gated tools are denied outright (ui.pyHeadlessUI.confirmanswersauto == 'allow'). An agent's reachable surface is exactlyallowtools on paths inside its worktree, plus--yesapprovals. - Tool allow/deny:
tools.allow(whitelist mode) andtools.denynarrow the registered schema the model sees. A web agent setstools.deny: [run_command, file_write], a code agent deniesweb_search, web_fetch. - Category permissions:
tool_permission(read/list/edit/bash/web>allow/ask/deny) is per-agent.bashdefaults toaskeverywhere, so settool_permission.bash: allowonly for agents that may run shell commands. - Plugins per agent: the
pluginsconfig list controls which plugin set loads in each process. Capabilities needing external dependencies (PDF extraction, MCP, vector search) are external plugins. Scoped ones (folder watching, text indexing) are bundled.
Example: a documentation agent¶
A doc agent watches a folder of PDFs, converts new ones to text, indexes them, and answers peers over the API. Decomposed:
| Capability | Home | Dependency |
|---|---|---|
| Folder watching (new-file detection) | internal bundled plugin | stdlib threading + pathlib |
| Text index over converted files | internal bundled plugin | stdlib |
| PDF > text extraction | external plugin | pypdf (lazy import) |
| Vector store / embeddings | external plugin | FAISS/Weaviate (later) |
| MCP server for peer agents | external plugin (planned) | mcp (lazy import) |
A minimal agent config (.polyglav/config.json in its own directory):
{
"provider": "ollama",
"model": "llama3.2",
"system_prompt": "You are the documentation agent. Convert PDFs to text, keep the index current, and answer questions from the stored docs.",
"tools.allow": ["glob", "file_read", "file_write", "run_command", "pdf2text", "watch_folder", "search_index"],
"tool_permission": { "read": "allow", "list": "allow", "edit": "allow", "bash": "allow", "web": "allow" },
"plugins": ["polyglav-core-fs", "polyglav-core-exec", "polyglav-core-doc-agent"]
}
Launch it, and peers talk to it over the same POST /chat API used everywhere:
polyglav serve --path agents/docs --port 8781 &
curl localhost:8781/chat -X POST -d '{"prompt": "What does spec-42.pdf say?", "session": "docs-pool"}'
Supervisor¶
polyglav fleet supervises the agents from the terminal: port allocation, health checks, a restart policy, and per-agent config generation. It is the systemd/Compose-shaped layer for a single host, so Docker is not needed. A fleet root is a directory holding the agents, typically one folder each, with two files in its .polyglav/:
.polyglav/fleet.json- the declarative roster. OneAgentDefper agent:name,dir,enabled,prefer_port,max_restarts(0 = unlimited), and an optionalcommandoverride (the test seam, real agents use the defaultpolyglav servecommand).polyglav/fleet.state.json- runtime state only (pids, ports, status, restart counts, last error), a snapshot the supervisor writes, never edited by hand
Build and run a fleet:
polyglav fleet init # scan subdirectories holding .polyglav/config.json
polyglav fleet config docs-agent --role research-agent # generate a config
polyglav fleet add code-agent --dir ../repo --port 8782
polyglav fleet up # foreground, Ctrl-C = graceful down
polyglav fleet up --detach # background daemon (polyglav fleet down stops it)
polyglav fleet status # agent/enabled/port/pid/state/restarts/error table
polyglav fleet restart code-agent # stop, reset backoff, relaunch on next sweep
polyglav fleet logs docs-agent -f # tail an agent's .polyglav/logs/<name>.log
While up runs, every sweep (default 2s) the supervisor does:
- Port allocation - agents get a free port by bind probe, preferring
prefer_port, scanning 8780-8890. Edited while running,add/inittake effect on the next sweep - Health checks - a
GET /healthprobe (2s timeout). A running agent that failsunhealthy_thresholdchecks (default 2) or whose process exits is treated as a failure - Restart policy - a failed agent is respawned after a backoff that starts at 5s and doubles to a 60s cap. Past
max_restarts(default 10) the agent goescrashedand the supervisor stops touching it untilpolyglav fleet restart <name>(orenable, which resets the counter and re-arms it). Settingenabled: falsein the manifest stops supervision and the agent - Graceful down - Ctrl-C,
polyglav fleet down, or aSIGTERMto the detached daemon sendsSIGINTto each child (the server's own graceful shutdown), then escalates toSIGKILLafter a grace period
Per-agent config generation¶
polyglav fleet config <name> writes only the keys you pass into <dir>/.polyglav/config.json, leaving existing keys intact, so an agent gets its personality before its first launch:
polyglav fleet config code-agent \
--provider ollama --model llama3.2 --mode build \
--system-prompt "You implement code." \
--tools-deny web_search \
--tool-permission "bash=allow" \
--role programmer
--role resolves the role (bundled + global + local registry) and inlines its system_prompt, model, and tool_permission into the generated keys, so the running agent needs no roles registry of its own. An unknown role aborts the write. --approve-model pre-approves the model referenced by --role or --model in the global models registry, so the headless serve agent can use it without prompting. Agent config is operator-managed: polyglav fleet config is the only intended writer, and a serve process has no config-write CLI path today. An engine-level guard making served agents immutable is an open TODO (see TODO).
Deployment¶
For a fleet you supervise each polyglav serve process with Docker Compose, scaling one service per agent from the repo's docker-compose.yml.example. Each service mounts the agent folder, publishes a host-local port, and carries restart: unless-stopped, so Compose restarts agents on failure and every agent exposes GET /health for monitoring. Full setup is in deploy.md.