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
//! The agent's **working-directory mark** — `refs/litany/cwd/<agent-id>`
//! (ARCH §3.3 *Working directory*).
//!
//! An agent's working directory is one mutable per-agent fact: its
//! worktree by default, and thereafter whatever its own `cd` tool call
//! last set. This module is that fact's one home. It lives in the
//! per-agent **mark** namespace ([`super::MARK_REF_ROOT`], §2.2) beside
//! `conflicted` / `budget-exhausted` / `abandoned` / `notify`, so it is
//! reaped with the agent by `litany delete` (§9.2 enumerates the mark
//! root, never a list of kinds), it crosses no fork and no transfer
//! (marks are keyed by agent id and nothing merges them), and it is not
//! context (§5.1 — the agent learns its cwd from the tool result, not
//! from its tree).
//!
//! **This mark carries a value where the others are bare assertions:**
//! the ref names a *blob* whose bytes are the absolute path. A ref may
//! name any object, so no second mechanism is needed to hold the one
//! extra fact — and `git gc` keeps the blob alive for exactly as long as
//! the mark does.
//!
//! The value round-trips through [`GitRunner::run_capture`], which
//! returns trimmed UTF-8, so [`write`] declines a directory whose path is
//! not preserved by that round trip rather than storing one that would
//! read back wrong (PRINCIPLES "Decline illegal operations").
//!
//! **The mark has two writers, and one validation.** The agent's own `cd`
//! built-in writes it mid-run; `litany prompt --cwd` / `litany dispatch
//! --cwd` seed it at creation, before the agent's first step (ARCH §3.3,
//! §2.5). Both reach a directory through [`resolve`], so a path is
//! refused in one voice wherever it was named — a second set of rules for
//! the seed would be a second answer to "what is a working directory".
use ;
use crateGitRunner;
use io;
use OsStrExt;
use ;
use Error;
/// Ref-namespace prefix for the working-directory mark (§3.3).
pub const CWD_REF_PREFIX: &str = "cwd/";
/// `refs/litany/cwd/<agent-id>` — the mark ref for one agent.
/// The agent's stored working directory, or `None` when the mark is
/// unset — which is the ordinary state of an agent that never called
/// `cd`, not an error. An unreadable mark (no repo, a ref pointing at a
/// non-blob, a git that would not run) reads the same way: the caller's
/// default applies, and no tool call is lost to a mark.
/// The agent's **effective** working directory (ARCH §3.3 *Resolution
/// at spawn*): the mark when it names a live directory, else the
/// agent's worktree. One home for the rule, because two readers ask it
/// — the executor, resolving where a tool subprocess runs
/// ([`crate::prompt::tool::spawn`]), and the tool window, deciding
/// which context files sit on that path
/// ([`crate::prompt::dispatch`]). A mark whose directory has since
/// disappeared answers the worktree rather than nothing: `cd` is itself
/// a tool call, so a hard decline would strand the agent somewhere it
/// could never leave.
/// Set the agent's working-directory mark to `dir` (an absolute path the
/// caller has already resolved and proven to be a directory). Writes the
/// path as a blob and points the mark at it — last write wins, exactly
/// as a `cd` should.
/// Every way a caller-named directory can fail to be a working
/// directory. One taxonomy for both writers (module docs): the `cd`
/// built-in re-emits it as its `is_error` `tool_result`, `--cwd` as the
/// verb's own refusal before the fork.
/// The absolute directory `path` names, ready to become a mark:
/// canonicalized, proven to be a directory, and proven to survive the
/// mark's round trip ([`storable`]).
///
/// **Relative paths need no resolution of ours.** `canonicalize` resolves
/// against this process's own working directory — which the executor set
/// to the agent's, when the caller is the `cd` built-in (§3.3), and which
/// is the operator's shell, when it is `--cwd`. Either way it is the
/// kernel's answer, `..` and symlinks included, not a re-derivation of
/// one. A path that names nothing and a path that names a non-directory
/// are declined separately: they are different mistakes.
/// Can `dir` survive the mark's storage round trip — written as bytes,
/// read back as trimmed UTF-8? A non-UTF-8 path or one with leading or
/// trailing whitespace cannot, and is declined here rather than stored
/// to read back as some other directory.