workspace-mgr 0.4.0

Fixed-policy repository workspace manager for coding agents
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# How this workspace works

## What this repository is for

This repository is a durable workspace for conversations between a user and
coding agents. `workspace-mgr` exists to make that kind of workspace practical:
it lets the user treat a coding agent as a general-purpose collaborator, not
only as a tool for editing an existing software project.

The user can start a chat and ask for any kind of work the agent can perform:
research a question, compare products, study a paper, write a report, analyze
data, prepare media, build software, organize records, or combine several of
those activities. The chat is the user-facing interface. The repository gives
that conversation a place to retain inputs, working materials, outputs, and
reproducibility evidence when the result should outlive the chat. The tools the
agent writes, the process it followed, and the conclusions the conversation
reached are retained regardless, because they are what makes the result
understandable later.

The user should normally describe the desired outcome rather than plan task
directories, branches, commits, or storage mechanics. The agent translates the
request into repository operations and reports the resulting task, artifacts,
and review state. The user may still make repository-level choices when they
matter, such as explicitly asking for an artifact to be stored in Git or S3,
requesting a shared repository change, or deciding when a pull request should
be merged.

`workspace-mgr` is the management interface behind this workspace. It gives the
agent a consistent way to create a task, bound its scope, place retained
content, publish a reviewable result, and coexist with other chats. It is not
the subject of the user's work; it is the mechanism that keeps the workspace
safe and understandable while the agent carries out that work.

The product applies the same management strategy to every initialized
repository. Repositories provide different Git and optional S3 locations, but
they do not select different task layouts, storage thresholds, cloud-usage
limits, review models, or agent responsibilities. This makes behavior portable:
a user and agent can move between managed repositories without relearning their
operating contract.

A chat that remains purely conversational or read-only does not need to create
repository state. As soon as a chat needs to create, change, download,
generate, or retain files, it becomes a writable conversation and owns exactly
one task in this workspace. This includes files it would otherwise create
outside the repository.

## From a chat to a task

For repository-writing work, the central relationship is:

```text
one writable conversation (chat) = one task = one target branch = one draft pull request
```

These are four views of one reviewable intention:

- The **conversation** contains the user's goal, decisions, and authorization.
- The **task** is the durable workspace for that conversation.
- The **target branch** is the task's publication lane.
- The **draft pull request** is the task's review and merge record.

An ordinary deliverable task directory holds the conversation's retained
inputs, working files, tools, evidence, and deliverables. It is where the work
happens, not only where finished results are filed: the agent writes its
scripts, runs its analyses, and keeps its intermediate materials inside it
rather than in a temporary directory elsewhere on the machine. Its README
explains the task's current purpose and important outputs; it is not a
transcript or chronological log. Its other files carry the durable record: the
decisions the conversation reached, the process it followed, the tools it
wrote, and the results that would be expensive or impossible to reproduce. The
product does not prescribe their names or layout, only that they are Markdown
files the README's directory map names. Its manifest records the task
identity, declared scope, target branch, and any higher cloud-usage limit the
user approved. An infrastructure task instead has an isolated worktree and a
private manifest because its content belongs at shared repository paths rather
than inside a timestamped deliverable directory.

Reading and ownership are separate. Any chat may inspect any repository path
when useful for context, including another chat's task directory. Reading a
path does not transfer ownership or authorize mutation.

For a deliverable task, its task directory is the default write boundary. The
agent must have explicit user authorization for the exact path and action
before it creates, edits, moves, or deletes anything outside that directory,
including a shared root path or another chat's task directory. If the current
request does not provide that authorization, the agent asks and waits before
writing. An additional-scope declaration records the approval and its reason;
it does not manufacture approval.

An infrastructure task is the structural exception to the task-directory
boundary because it has no deliverable directory. Its exact, user-authorized
manifest scopes are its write boundary. It may read elsewhere for context, but
it does not gain permission to mutate undeclared shared paths or another task's
directory. This makes the user's request auditable without turning
infrastructure work into permission to modify unrelated repository state.

`task create` establishes the local task, README, manifest, scope, and branch
identity. It does not publish a remote branch or create a pull request.
For a deliverable task, the agent immediately follows creation with an initial
plan and publication of that scaffold, then creates and verifies the draft pull
request before substantial task work. This is one automatic task-creation
checkpoint, not an extra action the user must remember to request.
`task status` resolves the current task and reports its scoped state without
publishing. `task rename` changes the current human-readable slug when the
conversation's topic evolves. For a deliverable it moves the complete task
directory, including Git and S3 placement metadata, while preserving the
immutable task ID and target branch. The next normal publication removes the
old remote tree and publishes the new path. For infrastructure it updates the
current slug in private task metadata while its identity-owned worktree remains
stable. `task discard` is the explicit opposite endpoint: after the user
decides that an unmerged task should not be retained and the agent closes or
verifies absence of its pull request, it removes that task's branch and local
workspace instead of publishing or merging it.

The same chat continues using the same task for its lifetime. Continue using
the same branch and pull request when refining that same intention. Start a new
task when work should be reviewed or merged independently. Unrelated chats must
not share a task, branch, or pull request.

The current slug is a mutable topic label; the task ID and target branch are
stable publication identities. This distinction preserves the one-PR contract:
renaming an open pull request's head branch can close that request on hosting
providers. After a task rename, the agent reuses the existing pull request and
updates its title and living description instead of replacing it.

A repository-infrastructure change follows the same relationship. It is still
one task with one branch and one pull request, but `task create --kind
infrastructure` gives it an isolated worktree, private task metadata, no
timestamped repository task directory, and an explicitly declared scope of
shared policy, root entrypoints, CI, or other repository-wide mechanisms.
Infrastructure is a kind of task, not a bypass around task ownership.

The draft-pull-request relationship is the review model for every managed
repository. It is part of the product strategy, not a per-repository option.

## Where the work happens

The task directory is the workplace, not only the filing cabinet. A chat that
builds its scratch workspace in a system temporary directory, a `mktemp`
directory, or the home directory keeps its polished output and loses everything
that explains it: the scripts it wrote, the commands it actually ran, the dead
ends, and the reason it chose one option over another. The agent therefore
creates those materials inside the task directory in the first place, rather
than producing them elsewhere and copying a result back.

Ephemeral output that is reproducible and will be discarded may live wherever
the environment puts it: package caches, virtual environments, build trees, and
throwaway command output are not task materials. The dividing line is whether
the task would want to consult the content later.

Two cases otherwise push work outside the repository, so each needs an explicit
answer. The first is content too large or too private to publish. It still
belongs in the task directory, where `storage set` and `untrack` decide what
leaves the machine; moving it out of the repository is not a way to avoid that
decision. The second is credentials. They are the single exception to the rule:
they stay outside the repository entirely, and the task records how to
regenerate them rather than their values.

Keeping the work inside the task directory is one half of the rule; curating
what leaves it is the other. Every file under a task is in one of two states.
It is selected — published in Git, or placement-recorded for S3 or local-only
retention — or it is ignored by a rule this repository tracks. There is no
third state in which a file simply sits in the task directory while everyone
remembers not to commit it. Publication stages the whole declared scope, so an
unignored file is published by the next publication whether or not anyone
intended it.

The by-products of the work are therefore not published by default. Build
output, caches, per-run logs and checkpoints, scratch copies of inputs, and
intermediate data that can be regenerated cheaply from the retained inputs and
tools are not what a reviewer or a later reader needs. The tools themselves,
the results that were expensive or impossible to reproduce, and the evidence
behind a claim are.

Ignore rules are layered so that each one sits where its audience can see it.
Rules that matter to one task belong in that task's own `.gitignore`, as the
narrowest rule that covers the junk, where they travel with the task, are
visible in its review, and stay inside the task's own write boundary. This
repository's own rules live in `.workspace-mgr/repository.gitignore`. Git has
no include directive, so `workspace-mgr` owns the root `.gitignore` as a whole
and generates it from that module plus a small fixed set of product rules for
output that is regenerated rather than retained; the root file is never
hand-edited, and `init` reconciles it like any other product-owned path. That
second layer is a pair of shared root paths, so reaching for it costs what any
change outside the task directory costs: the user's explicit authorization,
normally an infrastructure task, and a publication of its own before the rule
means anything in another clone. Routing bulk by-products to S3 is not a
substitute for either layer: S3 keeps Git small, it does not keep the workspace
curated.

Because no command can observe an agent writing to a temporary directory,
publication enforces only what reaches the index. It refuses a deliverable
publication that adds or changes content inside the task directory while that
directory documents nothing, refuses a symbolic link whose target is outside
the repository, and reports the ignored paths it found inside the task so the
agent can confirm that each one is reproducible output rather than work that
should have been retained. It refuses content that only a machine-local
ignore rule hides — the user's global excludes, `.git/info/exclude`, or an
ignore file whose matching bytes the publication does not carry — because such
a rule keeps the file out of every other clone and out of review. These
refusals come before the cloud-usage measurement described below, so the user
is asked about usage only for a publication that passes them. It reports
`bulk-publication` when one publication adds more than two hundred new files or
more than 256 MiB (268435456 bytes) of new content inside the task, as a check
rather than a refusal. Content placed
in S3 is judged by its pointer, so routing an expensive result out of Git does
not exempt it. A symbolic link inside a boundary already placed in S3 or kept
local with `untrack` is outside what publication can see, because that content
never reaches the index.

## Where task content lives

Task scope answers **which conversation owns a path**. Storage placement answers
**where the retained bytes live**. These are independent properties: putting an
artifact in S3 does not remove it from the task, and putting it in Git does not
broaden the task's scope.

Every retained path has one storage placement:

- **Git is the collaboration and control plane.** It is appropriate when
  content should be present in ordinary clones and its value comes from direct
  review, diff, merge, or joint evolution with repository source.
- **S3 is the artifact and data plane.** It is appropriate when content is
  consumed as an exact object, changes atomically, or should be hydrated on
  demand.
- **Local-only content stays on the current machine.** `untrack` preserves its
  bytes and records an explicit `local` placement plus a managed ignore rule.
  Git retains these small control records but excludes the payload; S3 content
  is cleaned after publication when no remote branch or tag still references
  it. New clones do not receive the payload.

The agent understands why content exists, so it makes the semantic choice when
that intent is clear and records the reason with `storage set`. The CLI does not
guess from filename extensions. A user's explicit placement instruction takes
priority at any size.

Size is a fallback for new content whose semantics have not been selected. A
new boundary below 1 MiB strongly defaults to Git. From 1 through 10 MiB, Git
remains the fallback but the agent is asked to review whether collaboration or
artifact history is actually intended. Above 10 MiB, S3 is the fallback. A
standalone S3 boundary below 1 MiB is normally inefficient because its metadata
and remote operations may outweigh the payload; an explicit selection still
succeeds with a warning. A meaningful directory may instead be selected as one
boundary, whose reported size is the aggregate size of its materialized regular
files. Automatic evaluation treats unclassified files independently; selecting
a directory boundary is an intentional semantic operation and does not promise
that the storage backend packs it into one remote object.

Published placement is stable. Content does not silently move between Git and
S3 merely because its size changes. An explicit placement remains in force
until it is reset. A directory placed in S3 is one recursive boundary whose
descendants inherit that placement; overlapping boundaries are rejected so a
path never has competing placement owners.

Placement operations describe or change local intent:

- `storage status` explains the target, boundary, selection basis, semantic
  reason, payload size/file count, and any structured warning.
- `storage set` records an explicit Git or S3 choice.
- `storage reset` removes that choice and returns the path to fixed policy.
- `untrack` explicitly keeps a file or complete boundary local only. Repeated
  publication does not track it again, and post-merge `refresh` preserves any
  existing local bytes. Re-track it with `storage set --to git|s3`; a local-only
  choice cannot be cleared with `storage reset`.
- `move` changes a path while preserving its placement.
- `storage hydrate` retrieves exact S3 content into the local workspace.

None of these operations publishes a task. Placement and publication are
separate so the agent can organize the proposed result before making it visible
remotely.

Retained content also costs the user cloud storage, so every task has a
cloud-usage limit: 1 GiB (1073741824 bytes) unless the user approves a higher
limit for that task. Cloud usage is what the task keeps on the remotes: the Git
history its branch adds beyond the base branch, including Git LFS objects,
every retained S3 object version of its paths, and the uploads its next
publication would add. Content kept local only is not uploaded and does not add
to it. The limit is the user's decision, not a placement choice: moving content
into another task, location, or service does not change who must approve it.

## From local work to review

Files in the task directory and local placement choices are proposed task
state. `plan` explains what the task would publish: its exact scope, placement
decisions, and Git changes. It may inspect remote state, but it does not upload
task content, create a revision, or advance a remote branch.

`publish` is the remote visibility boundary. It is the only repository command
that writes a task state to remotes. For S3-placed content, it uploads and
verifies the exact object versions first. It then constructs a Git commit from
only the task's declared scopes, advances the target branch, and verifies the
remote revision. The Git revision is the publication point for the combined
state: a published branch must never refer to missing S3 content.

Publication is also where the cloud-usage limit is enforced. `plan` reports
the task's published and projected Git and S3 usage, its limit, and the largest
contributors. `publish` refuses a publication that would take the task past its
limit before it places, commits, or uploads anything, and checks again before
the upload and before the Git commit because content can change while it runs.
A publication that only removes content, apart from at most 1 MiB
(1048576 bytes) of new workspace-mgr control-file content per publication,
where metadata that only drops entries is free, remains allowed while the task
is over its limit.

When a task would exceed its limit, it waits for the user's decision. The agent
stops all work on the task, reports the published and projected usage with the
largest contributors, proposes one specific higher limit, names the cleanup
alternatives, and ends its turn. Only the user's answer in that chat can
approve a higher limit. `task approve-cloud-usage` records that answer in the
task manifest; recording an approval documents the user's decision and never
creates it. The next publication carries that manifest change, so the pull
request shows the approved limit, and each publication commit also names it in
a `Cloud-Usage-Approval` trailer. If the user declines, the agent performs only
the cleanup the user chooses and publishes the reduction on its own: while the
task is over its limit, a task that documents nothing may still remove or
untrack content, and a record added to that publication would be growth the
limit refuses, so the record follows in the first publication the limit allows.
Published Git history cannot shrink, so when it alone exceeds the limit, only an
approval or discarding the task resolves the decision.

Publishing makes the target branch ready for review; it does not merge it. The
agent maintains the corresponding pull request through the repository's hosting
workflow. `workspace-mgr` does not create, edit, approve, or merge that pull
request through a hosting-provider API. Merging remains an explicit authorized
action.

Immediately after the initial scaffold publication, the agent must query by
head branch, reuse an existing open pull request or create exactly one draft
pull request, and never create a duplicate. Infrastructure tasks do this after
their first safe scoped publication because their creation has no repository
content to publish. The agent owns the title and living description and updates
them whenever the goal, scope, deliverables, validation, or known limitations
materially change. It then verifies the base branch, head branch, review state,
and that the pull-request head revision equals the revision reported by
`publish`.
Provider failures are blockers to full synchronization. The agent must not
merge, enable auto-merge, approve, close, or mark the request ready without the
user explicitly authorizing that exact transition. A user request to discard a
specific unmerged task is exact authorization for the agent to close that
task's pull request, verify it is closed, and then run confirmed task cleanup.

A task is fully synchronized when its local and remote branch revisions match,
its pull-request description reflects the same intention, and a final plan
reports no remaining task changes.

Before ending every turn in a writable task, the agent automatically reconciles
that state. It records the turn's decisions, process, tools, and
hard-to-reproduce results in the task's own files when the turn produced any,
plans the task, publishes all safe retained in-scope changes even when the
deliverable is still work in progress, updates and verifies the draft pull
request, and finishes with a no-change plan. A turn with no publishable
change still verifies that the local task revision, remote branch, and
pull-request head already match. The user does not need to request this
turn-ending synchronization. If publication or provider verification is
blocked, the agent reports the exact unsynchronized state rather than claiming
the task is current. A task waiting for the user's cloud-usage decision is such
a blocker: reconciliation stops at the plan, before the turn is recorded or
any by-product is curated, and the agent reports the paused state, its
question, and the changes that remain unpublished instead of publishing. The
record of that turn follows once the user has answered.

If Git publication fails after an S3 upload, an unreferenced S3 object version
may remain, but no remote Git revision should point to missing content.
Retrying the same publication is safe. After Git publication succeeds, the CLI
permanently deletes every version at S3 object paths removed by deletion, move,
rename, untrack, or S3-to-Git placement. A current remote branch or tag defers deletion
until a later publish, refresh, or discard observes that the reference is gone.

Discard is deliberately a two-step destructive operation. Its dry run records
the observed local task ref, remote task ref, and shared-branch revisions in
private confirmation state and reports every local action plus the current S3
version references. Confirmation from the shared checkout is accepted only for
the exact task ID and unchanged revisions. The CLI then deletes the remote task
branch with an exact lease, deletes the local task ref, removes the deliverable
directory or infrastructure worktree, and restores any declared shared paths to
the local shared-branch tree. A remote failure restores quarantined local state.
Merged tasks are refused. Versioned S3 object paths are queued before branch
deletion, then permanently purged when no current remote branch or tag protects
them.

## How multiple chats share the workspace

The checkout remains on its shared branch while multiple chats may have
independent task directories and working-tree overlays.
A task publishes to its own branch without checking that branch out and without
staging paths owned by other tasks in the shared Git index.

An unrelated untracked or modified path may therefore be valid state owned by
another active conversation. Broad stash, clean, reset, or deletion operations
could erase another task's work and are not routine synchronization tools.
Agents may read that state for context, but must not mutate it without explicit
user approval for the exact path and action. Untracked does not mean unowned.

After the user merges a task, `refresh` safely advances the shared branch,
preserves unrelated overlays, and materializes incoming Git and S3 content. One
stored boundary the storage engine cannot address does not hold that up: refresh
advances the branch, hydrates everything else, and names the boundary it left
unhydrated together with the rename that recovers it, because refusing inbound
synchronization for every checkout is the larger harm. Refresh is inbound
synchronization; it does not publish a task.

## The workspace lifecycle

The complete story is:

1. A repository owner uses `setup` and `init` to establish the managed
   workspace, tracked repository facts, and thin agent bootstrap.
2. The user starts a chat and describes the outcome they want.
3. The agent loads `instructions` and decides whether the conversation is
   read-only or needs a writable task.
4. For writable work, `task create` gives the chat one durable workspace,
   scope, branch identity, and pull-request identity.
5. For a deliverable task, the agent immediately publishes the initial scaffold
   and creates the matching draft pull request. Infrastructure creates it after
   the first safe scoped publication.
6. The agent performs the requested work inside that task and keeps its README
   aligned with the current purpose and outputs. If the topic changes, `task
   rename` updates its current slug without replacing its branch or review.
7. Retained artifacts are placed in Git or S3 automatically or by an explicit
   user or agent choice. An explicit `untrack` choice keeps content local only.
8. At every turn end, the agent records what the turn decided, did, wrote, and
   produced in the task's own files, `plan` explains the proposed reviewable
   state, `publish` advances the task branch when needed, and the agent updates
   and verifies the matching draft pull request.
9. If a publication would take the task past its cloud-usage limit, the task
   pauses. The agent asks the user, who approves a higher limit or chooses
   cleanup, and no further task work happens until the user answers.
10. The matching draft pull request carries review, and the user or maintainer
    decides whether to merge it or explicitly abandon the task.
11. After merge, `refresh` brings the result into the shared workspace without
    disturbing other active chats. After abandonment, the agent closes the
    unmerged pull request and `task discard` removes the task workspace and
    branch, permanently purging its unreferenced S3 object paths.

`config show` reports the repository's Git and S3 facts, and `doctor` diagnoses
the CLI, repository, Git, and storage environment without changing repository
state.

The repository also records the oldest `workspace-mgr` release that can read its
task state. `workspace-mgr` maintains that declaration itself: when a
publication introduces task state that older releases cannot read, such as a
manifest that records a cloud-usage approval, the publication raises it, and
nothing lowers it once it is merged. An older release refuses the repository
instead of misreading it, and the agent tells the user both versions and asks
before updating, just as it does for an update notice.

The intended division of responsibility is simple: the user asks for outcomes,
the agent performs the work inside one task, and `workspace-mgr` preserves the
workspace boundaries that make the result durable, reviewable, and safe to
combine with other conversations.