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
//! `mecha-core` — an agent harness for local models.
//!
//! The library knows nothing about any particular CLI, UI, or project. It gives
//! you four things and lets you wire them together:
//!
//! * [`provider`] — talk to a model (Anthropic, or anything OpenAI-shaped)
//! * [`tool`] — things the agent can do, native or [`mcp`]-backed
//! * [`agent`] — the loop that puts those together
//! * [`session`] / [`batch`] — persistence and fan-out around the loop
//!
//! ```no_run
//! # async fn example() -> anyhow::Result<()> {
//! use mecha_core::{agent::Agent, agent::Conversation, config::Config};
//! use mecha_core::sandbox::Sandbox;
//! use mecha_core::tool::{ModeApprover, Registry, ToolCtx};
//! use std::sync::Arc;
//!
//! let cfg = Config::load(&std::env::current_dir()?)?;
//! let (_, provider_cfg) = cfg.provider(None)?;
//!
//! // How `shell` is confined. It decides that tool's declared capabilities,
//! // so it is built before the registry rather than consulted at call time.
//! let sandbox = Arc::new(Sandbox::new(cfg.sandbox.clone()));
//!
//! let agent = Agent::new(
//! mecha_core::provider::build(provider_cfg)?,
//! Registry::new().with_builtins(&cfg.tools, sandbox),
//! Arc::new(ModeApprover { mode: cfg.tools.permission_mode }),
//! ToolCtx {
//! workspace: std::env::current_dir()?,
//! shell_timeout: std::time::Duration::from_secs(cfg.tools.shell_timeout_secs),
//! security: cfg.security.clone(),
//! ..ToolCtx::default()
//! },
//! cfg.agent.clone(),
//! None,
//! )?;
//!
//! // A conversation carries its own taint, so keeping it across turns keeps
//! // the trifecta interlock honest — see `agent::Conversation`.
//! let mut convo = Conversation::user("What changed in this repo today?");
//! let outcome = agent.run(&mut convo, None).await?;
//! println!("{}", outcome.text);
//! # Ok(())
//! # }
//! ```
pub use ;
pub use Config;
pub use ;
pub const VERSION: &str = env!;
/// Create a directory (and its parents) and make the leaf owner-only.
///
/// Transcripts, staged outbox drafts, the learning store and spilled tool
/// output all carry the user's private data — mail bodies included, now that
/// mail is wired — so their directories get the rule the mail token files
/// already enforce on themselves (0600). The leaf only, on purpose: parents
/// like `~/.mecha` also hold things the user may deliberately share, and the
/// sensitive data lives below the leaf. Idempotent, and tightens a
/// pre-existing directory too.
/// Is this pid still around? `kill(pid, 0)` checks without delivering
/// anything; `EPERM` means it exists and is not ours, which still counts.
///
/// The range check is not defensive padding — it is the whole correctness of
/// the function. `kill(2)` gives non-positive pids entirely different
/// meanings: `0` is "every process in my group", `-1` is "every process I may
/// signal" (which succeeds, always), and any other negative is a process
/// group. A corrupt marker holding one of those would report a long-dead run
/// as alive and leave whatever owns the marker looking permanently busy in
/// every UI that asks. Found by a test using `u32::MAX`, which sign-flips to exactly the
/// `-1` case.