-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgarrison.toml
More file actions
320 lines (301 loc) · 16.4 KB
/
Copy pathgarrison.toml
File metadata and controls
320 lines (301 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# Garrison server configuration.
#
# One `garrison-agent serve` per user per machine reads this file. Editors
# spawn `garrison-agent acp`, which is a relay to that daemon's socket and
# runs no engine of its own, so this file is the one configuration in force
# for every editor window and every terminal chat on this seat.
#
# Where the daemon looks: ./garrison.toml, then
# $XDG_CONFIG_HOME/garrison/garrison.toml (~/.config/garrison/garrison.toml).
# A daemon started by a relay, or by systemd, runs with its working directory
# at $HOME and is handed only the XDG file; the relay's own --config flag is
# never passed on, so a workspace-local file cannot become every later
# editor's policy.
[server]
# The Unix socket the daemon listens on and every client connects to. The
# default is $XDG_RUNTIME_DIR/garrison-agent.sock (per user, mode 0700), so
# it is left unset here. A --socket flag overrides it on either side.
# socket = "/run/user/1000/garrison-agent.sock"
#
# Whether a client that finds no daemon may start one. On (the default), the
# relay starts the daemon through `systemctl --user start garrison-agent` when
# that unit is loaded, otherwise as a detached child rooted at $HOME. Off, a
# missing daemon is an error the client reports; only an operator or systemd
# starts the engine. `garrison-agent ping` never starts anything either way.
autostart = true
# How long a client waits for an autostarted daemon to answer.
start_timeout_secs = 10
[threads]
# The boundary rule for a shared daemon. project_root defaults to the working
# directory the daemon starts in: under systemd that is %h, and under relay
# autostart it is $HOME, so any workspace under the home directory can host a
# session and each session is still confined to its own cwd. Workspaces
# elsewhere (/srv, /opt) must be listed in workspace_roots or the session is
# refused as "outside the approved roots". An administrator narrows the
# default by setting project_root to one tree.
# project_root = "/home/dev/projects"
# workspace_roots = ["/srv/projects/other"]
system_prompt = "You are Garrison, a governed coding agent. Work inside the project root. Read before you write, prefer small verifiable steps, and explain what you changed."
[approval]
timeout_secs = 300
# Read-only tools skip the permission round-trip; anything that can change
# state still reaches the operator.
#
# This list is read only on a standalone install. The moment a [plane] section
# is present and a policy bundle is in force, it is not consulted at all: a
# file on this machine must not be able to widen a policy the organization
# published. `garrison-agent ping` says so when that is happening.
auto_approve = ["read_file", "glob", "grep", "list_directory", "calculate", "get_context_remaining", "update_plan", "lsp_diagnostics", "lsp_hover", "lsp_definition", "lsp_references"]
# [policy]
# How this install pulls and enforces the policy bundle the control plane
# assigned it. Ignored entirely without a [plane] section: there is nothing to
# pull from.
#
# Nothing in this section can name a bundle, edit a rule, or turn enforcement
# off. Every key can only make this machine stricter or make it ask sooner. A
# machine that could opt out of its policy from a local file would not be
# centrally governed, whatever the console showed.
#
# THE RULE, in full:
#
# 1. A daemon without [plane] governs itself from this file. That is the only
# case in which [approval].auto_approve is read.
# 2. A daemon with [plane] runs turns only under a bundle the plane assigned
# to it. It checks that the bundle is published, that its content hashes to
# the checksum the plane recorded, and that every command rule matches its
# own examples. It then writes the bundle id and checksum to its
# AgentInstall row, which is what promotes the install from `enrolled` to
# `active`.
# 3. If the plane cannot be reached, the daemon keeps running on the last
# bundle it verified, for at most offline_grace_secs after that bundle was
# fetched. `_garrison/status` reports source = "cache" for the whole time.
# 4. If the plane answers and the answer is anything other than a verified
# bundle (no assignment, unpublished, checksum mismatch, a rule that fails
# its own examples, an install quarantined or retired, a credential
# refused), or the grace runs out, the daemon starts, answers status with
# the reason, and refuses every turn. It never falls back to this file.
# 5. Local edits here cannot widen a governed daemon: while governed, the
# auto-approve list above is not consulted, and `bash` is decided only by
# the bundle's command rules.
#
# NOT ENFORCED in this release: a bundle's `network_egress` and
# `allow_unsandboxed_escalation` are pulled, checksummed, and reported, and
# nothing acts on them. `garrison-agent ping` prints that so an author cannot
# come to believe otherwise.
#
# How often to re-ask the plane. This is not a latency knob: nothing on the
# turn path waits for the network. It bounds how long this machine keeps
# enforcing a bundle that has since been republished or unassigned.
# refresh_secs = 300
#
# How long a verified bundle may still be enforced after the plane stops
# answering. Measured from when the plane last handed it over, not from when
# this process started, so restarting the daemon does not buy another window.
# Set 0 to forbid running on a cached bundle at all.
#
# A CAP, NOT A GRANT: when the control plane supplies a bound of its own for
# the organization's impact level, the shorter of the two applies. This file
# may shorten how long a machine runs offline and may never lengthen it.
# offline_grace_secs = 86400
#
# Where the verified bundle is cached. Defaults to bundle.json in the Garrison
# config directory, at mode 0600, beside this install's identity.
# cache_path = "/home/dev/.config/garrison/bundle.json"
# Language servers, routed by file extension. Deliberately none by default.
#
# Servers are spawned once, at launch, rooted at the daemon's project root.
# With one daemon per user that root is $HOME, not a workspace, so a server
# started here would index the wrong tree and the lsp_* tools would answer
# from nothing while looking as if they worked. Rather than ship that
# silently degraded, the block is off until servers are spawned per session
# root (a tracked follow-up). Enable it only on a daemon whose
# [threads] project_root names the one tree the server should index.
# [lsp_servers.rust]
# command = "rust-analyzer"
# extensions = ["rs"]
[audit]
# What Garrison requires of the trail acton-ai writes. acton-ai owns the trail
# itself: where it is, what an append promises, who holds its lock. This
# section owns the three questions it has no opinion about.
# What an append must promise before a turn may run. Omitted, this follows
# acton-ai.toml's own `[audit] durability`; naming it here is how a deployment
# states the requirement in its own file. `strict` is what arms the turn gate.
# durability = "strict"
# Where the last verified chain head is written, outside the trail. This is
# what makes a tail truncation detectable: the trail alone still verifies
# after its last entries are deleted, because a prefix of a valid chain is a
# valid chain. Only a record of where the chain used to end can notice.
# Defaults to $XDG_STATE_HOME/garrison/audit-anchor.json (mode 0600), rewritten
# after every finished turn.
# anchor_path = "/home/dev/.local/state/garrison/audit-anchor.json"
# What startup does when the trail and the anchor disagree. "refuse" (the
# default) stops the daemon with exit 2 on a truncated, rewritten, or replaced
# trail: starting over it would append to a record already known to be
# incomplete. "warn" logs the same finding and starts anyway. A trail that
# merely grew past its anchor is never a mismatch; that is what a daemon that
# died before re-anchoring looks like.
# on_anchor_mismatch = "refuse"
# Whether the daemon may start without an armed trail. Omitted, a trail is
# required exactly when a [plane] section is present: an install answering to
# an agency records what it does, a standalone developer install need not.
# Set it explicitly to override the inference in either direction.
# required = true
[sessions]
# What Garrison requires of the sessions acton-ai stores. acton-ai owns the
# store itself: which database file, what a checkpoint holds, how a turn is
# resumed. That is `[checkpoint]` in acton-ai.toml, and its presence is what
# arms persistence. This section owns the two questions it has no opinion
# about, because they are the agency's questions rather than the runtime's.
# Whether the daemon may start without a session store. Omitted, a store is
# required exactly when a [plane] section is present, which is the same rule
# [audit] required follows and for the same reason: an operator whose work
# vanishes on every upgrade has not been given a governed agent, only an
# unreliable one. A required store that acton-ai.toml does not arm is a
# refusal to start (exit 2). Set it explicitly to override the inference in
# either direction.
# required = true
# Days a session may go untouched before the daemon deletes it, with its
# conversation and every message in it. Persistence without retention is a
# growing disk and a growing disclosure: every prompt an operator ever typed,
# kept forever, on a machine an agency has to be able to say something
# definite about.
#
# One thing is never swept, at any age: a session holding a turn a restart
# interrupted. The operator has not yet said whether to resume or abandon it,
# and deleting it would make that decision for them.
retain_days = 30
# How often that window is enforced. The first sweep runs at startup. Zero in
# either key is read as one rather than as "never" and rather than as
# "immediately".
sweep_interval_hours = 24
# What a client does about an interrupted turn is not configured here; it is
# asked for, per turn, over the protocol. `session/load` reports the turn in
# `_meta.garrison.interruptedTurn`, and the client answers with
# `_garrison/session/resume` (carry on from the round the checkpoint stopped
# at) or `_garrison/session/abandon` (give up on it and unblock the session).
# Until one of those arrives the session refuses new prompts with JSON-RPC
# code -32019, and a resume or abandon with nothing to act on is -32021.
[experimental]
# Features that ship in this binary and are off until asked for.
#
# Enabling one accepts a narrower promise than the rest of this binary makes:
# its behaviour, its output, and its exit codes may change without a major
# version bump. Nothing else here loses its usual contract.
#
# The refusal is deliberate. A feature nobody can reach never stops being
# experimental, and a printed warning is read once and then filtered out of CI
# logs. A refusal is read every time until somebody decides, and the decision
# leaves a trace an auditor can find: this line, or GARRISON_EXPERIMENTAL in a
# pipeline definition.
#
# GARRISON_EXPERIMENTAL is comma-separated and enables a feature for one
# invocation. Either source is enough and neither can veto the other.
# Unattended pull request review: `garrison-agent review`, read-only, exits
# with a code a pipeline branches on. See docs/review-mode.md.
review = false
# [plane]
# Enrolling with a control plane. Omit this section entirely and the agent runs
# standalone, exactly as it did before enrollment existed.
#
# The first start that finds this section and no install record redeems an
# enrollment packet: a single-use grant left on this machine by whoever
# provisions the fleet. The packet is deleted once spent, and the identity the
# plane assigns is written to install.json beside this file's config directory.
# Every later start reads that record and makes no call, so a plane outage
# cannot ground a fleet that has already been admitted.
#
# A machine that has *not* enrolled and is refused, or cannot reach the plane,
# does not start (exit 2, which the systemd unit does not retry). A governed
# agent that runs anyway is not governed.
#
# url = "https://plane.agency.gov"
#
# Where the install-token exchange lives. Defaults to `url`, which is the
# deployment the shipped control-plane config describes: garrison-hooks sits
# behind the same name as the plane.
#
# Once enrolled the daemon holds an Ed25519 private key and no bearer. Before
# every plane call it signs a 120-second assertion and posts it to
# POST /api/v1/install/token here, receiving a 15-minute token scoped to this
# install's organization. Set this only when that route is reverse-proxied
# somewhere else, or when a developer runs the hook service on its own port.
# hooks_url = "https://hooks.agency.gov"
#
# Where to look for the enrollment packet. Defaults to enrollment.toml in the
# Garrison config directory (~/.config/garrison/enrollment.toml).
# enrollment_packet = "/etc/garrison/enrollment.toml"
#
# Who this install belongs to, when the grant itself does not name an operator.
# The plane resolves it to a registered operator and refuses if it cannot, or
# if that operator is not active or is not known to the directory. It is
# consulted once, at first enrollment: the install binds to the operator's
# row, so a later rename in the directory does not affect an enrolled install.
# operator_upn = "dev@agency.gov"
#
# How often the daemon reconfirms that it still holds a seat. Default 60
# seconds, clamped to 15..900. One check is three reads through the same
# bearer every other subsystem spends: this install's row, its operator's
# seats, and the organization. A turn is gated on the answer already in hand,
# so this is what decides how quickly a revocation reaches a running daemon,
# not what a prompt waits on.
# seat_check_secs = 60
#
# A ceiling on how long a confirmed seat may be spent while the plane cannot
# be reached. The window itself comes from the organization's impact_level and
# the seat's tier (72h down to zero; see docs/control-plane.md, "Seat
# entitlement"). This setting may only shorten that window and can never
# lengthen it: a deployment may be stricter than its impact level requires,
# never more generous. A revoked seat gets no window at all, whatever this
# says, because a refusal is an answer rather than an outage.
# offline_grace_secs = 3600
# [plane.shipping]
# Getting the audit trail off the machine that wrote it.
#
# The local trail is hash-chained, which proves nobody edited the middle of it.
# It proves nothing about the end, because a prefix of a valid chain is itself
# a valid chain: an operator who deletes the last hour and restarts leaves a
# file that verifies perfectly. The only defence is a copy the machine cannot
# reach. A shipper walks the trail forward and posts each sealed entry to the
# plane, which re-links it against what it already holds and refuses forks and
# edits.
#
# Every value below is a default; the section may be omitted entirely.
#
# Whether to ship at all. Off, the daemon still writes and verifies its local
# trail, and _garrison/status reports shipping as "disabled" rather than
# omitting it: an absent answer is not one an auditor can use.
# enabled = true
#
# How often to look for new entries, in seconds. The shipper also ships at the
# end of every turn, so this is the floor rather than the usual cadence.
# poll_interval_secs = 5
#
# How often to report the trail's own row (local head, shipped-through), in
# seconds. That row is what the plane's liveness sweep watches for silence.
# report_interval_secs = 60
#
# How many entries one batch may carry. Entries go up one at a time in chain
# order regardless; this bounds how long one batch holds the shipper.
# batch = 50
#
# When an unshipped backlog stops the work. This is the part worth reading
# twice, because the shipper is a gate: past either bound, and with
# fail_closed set, AdmitTurn refuses and the daemon stops taking turns.
#
# - An unreachable control plane never stops a turn on its own. The trail
# file is the buffer, a laptop on a train is not a governance failure, and
# a daemon that stopped working whenever a VPN dropped would be turned off
# within a week.
# - A backlog past its bound does stop turns. A day, or ten thousand
# entries, is not an outage anybody is still calling an outage.
# - A halt stops turns always, whatever fail_closed says. The plane refused
# an entry as forked or edited, the credential was rejected, or the local
# trail was rewritten under the cursor. None of those heal by waiting.
#
# max_unshipped_age_secs = 86400
# max_unshipped_entries = 10000
# fail_closed = true
#
# How long to wait after a failed batch, doubling to the ceiling, in seconds.
# backoff_base_secs = 1
# backoff_ceiling_secs = 300