sabbatical 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sabbatical-0.1.0/LICENSE +21 -0
- sabbatical-0.1.0/PKG-INFO +312 -0
- sabbatical-0.1.0/README.md +257 -0
- sabbatical-0.1.0/pyproject.toml +48 -0
- sabbatical-0.1.0/src/sabbatical/__init__.py +1 -0
- sabbatical-0.1.0/src/sabbatical/__main__.py +4 -0
- sabbatical-0.1.0/src/sabbatical/api/__init__.py +1 -0
- sabbatical-0.1.0/src/sabbatical/api/app.py +107 -0
- sabbatical-0.1.0/src/sabbatical/api/broadcast.py +51 -0
- sabbatical-0.1.0/src/sabbatical/api/dependencies.py +20 -0
- sabbatical-0.1.0/src/sabbatical/api/routers/__init__.py +1 -0
- sabbatical-0.1.0/src/sabbatical/api/routers/agents.py +317 -0
- sabbatical-0.1.0/src/sabbatical/api/routers/organizations.py +182 -0
- sabbatical-0.1.0/src/sabbatical/api/routers/runs.py +133 -0
- sabbatical-0.1.0/src/sabbatical/api/routers/status.py +48 -0
- sabbatical-0.1.0/src/sabbatical/api/routers/tasks.py +469 -0
- sabbatical-0.1.0/src/sabbatical/api/schemas.py +220 -0
- sabbatical-0.1.0/src/sabbatical/cli/__init__.py +1 -0
- sabbatical-0.1.0/src/sabbatical/cli/agent_cmds.py +168 -0
- sabbatical-0.1.0/src/sabbatical/cli/formatters.py +108 -0
- sabbatical-0.1.0/src/sabbatical/cli/main.py +25 -0
- sabbatical-0.1.0/src/sabbatical/cli/org_cmds.py +131 -0
- sabbatical-0.1.0/src/sabbatical/cli/run_cmds.py +81 -0
- sabbatical-0.1.0/src/sabbatical/cli/server_cmds.py +123 -0
- sabbatical-0.1.0/src/sabbatical/cli/task_cmds.py +203 -0
- sabbatical-0.1.0/src/sabbatical/core/__init__.py +0 -0
- sabbatical-0.1.0/src/sabbatical/core/agent/__init__.py +1 -0
- sabbatical-0.1.0/src/sabbatical/core/agent/runtime.py +60 -0
- sabbatical-0.1.0/src/sabbatical/core/agent/tools.py +301 -0
- sabbatical-0.1.0/src/sabbatical/core/config.py +69 -0
- sabbatical-0.1.0/src/sabbatical/core/context_builder.py +207 -0
- sabbatical-0.1.0/src/sabbatical/core/cost.py +54 -0
- sabbatical-0.1.0/src/sabbatical/core/db.py +177 -0
- sabbatical-0.1.0/src/sabbatical/core/description_generator.py +100 -0
- sabbatical-0.1.0/src/sabbatical/core/dispatcher.py +258 -0
- sabbatical-0.1.0/src/sabbatical/core/logging_setup.py +35 -0
- sabbatical-0.1.0/src/sabbatical/core/tag_parser.py +26 -0
- sabbatical-0.1.0/src/sabbatical/core/worker.py +472 -0
- sabbatical-0.1.0/src/sabbatical/mcp/__init__.py +0 -0
- sabbatical-0.1.0/src/sabbatical/mcp/server.py +303 -0
- sabbatical-0.1.0/src/sabbatical/migrations/README +1 -0
- sabbatical-0.1.0/src/sabbatical/migrations/env.py +80 -0
- sabbatical-0.1.0/src/sabbatical/migrations/script.py.mako +28 -0
- sabbatical-0.1.0/src/sabbatical/migrations/versions/a1b2c3d4e5f6_remove_sessions.py +52 -0
- sabbatical-0.1.0/src/sabbatical/migrations/versions/b569fc0a5c4c_initial.py +103 -0
- sabbatical-0.1.0/src/sabbatical/migrations/versions/d47fbc9edd39_add_model_column_to_agents.py +32 -0
sabbatical-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011-2026 The Bootstrap Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sabbatical
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local AI agent orchestration CLI — async task collaboration for developers
|
|
5
|
+
License: The MIT License (MIT)
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2011-2026 The Bootstrap Authors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in
|
|
17
|
+
all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
25
|
+
THE SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Keywords: ai,agents,cli,orchestration,llm,automation
|
|
28
|
+
Author: elpapi42
|
|
29
|
+
Requires-Python: >=3.12,<4.0
|
|
30
|
+
Classifier: Development Status :: 3 - Alpha
|
|
31
|
+
Classifier: Environment :: Console
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
38
|
+
Classifier: Topic :: Utilities
|
|
39
|
+
Requires-Dist: alembic (>=1.13.0)
|
|
40
|
+
Requires-Dist: databases[aiosqlite] (>=0.9.0)
|
|
41
|
+
Requires-Dist: fastapi (>=0.111.0)
|
|
42
|
+
Requires-Dist: google-adk[extensions] (>=0.1.0)
|
|
43
|
+
Requires-Dist: httpx (>=0.27.0)
|
|
44
|
+
Requires-Dist: mcp (>=1.26.0)
|
|
45
|
+
Requires-Dist: pydantic (>=2.7.0)
|
|
46
|
+
Requires-Dist: sqlalchemy (>=2.0.0)
|
|
47
|
+
Requires-Dist: sse-starlette (>=2.1.0)
|
|
48
|
+
Requires-Dist: typer[all] (>=0.12.0)
|
|
49
|
+
Requires-Dist: uvicorn[standard] (>=0.30.0)
|
|
50
|
+
Project-URL: Homepage, https://github.com/elpapi42/sabbatical
|
|
51
|
+
Project-URL: Issues, https://github.com/elpapi42/sabbatical/issues
|
|
52
|
+
Project-URL: Repository, https://github.com/elpapi42/sabbatical
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
# Sabbatical
|
|
56
|
+
|
|
57
|
+
**A local AI agent orchestration system built around async task collaboration — not chat sessions.**
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
Most people use AI by opening a chat window, typing a request, and waiting. The AI responds. You react. It's a conversation — synchronous, serial, one thing at a time. You're blocked until it finishes, and it's blocked until you respond.
|
|
62
|
+
|
|
63
|
+
Sabbatical is a different model. You write a task spec. You assign it to an agent. The agent picks it up, does the work using real tools in your actual codebase, hands it off to another agent via an `@mention`, and those agents keep working until the task surfaces back to you — done, blocked, or ready for review. Meanwhile, you're working on something else.
|
|
64
|
+
|
|
65
|
+
It's the difference between pair programming on a video call and managing a team through tasks. The team model scales. The call doesn't.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
Sabbatical runs a **local API server** on your machine. The server manages a **database-as-queue**: it continuously polls for tasks assigned to agents, spins up worker threads, runs agents against real tools (file reads, file writes, shell commands), and processes their output. There is no cloud dependency. Everything — the database, the agent workspace, the execution — lives on your machine.
|
|
72
|
+
|
|
73
|
+
### The Core Concepts
|
|
74
|
+
|
|
75
|
+
**Organizations** are isolated workspaces. Each organization has a `workspace_path` (a directory on your machine) and a roster of agents. Agents in different organizations cannot interact.
|
|
76
|
+
|
|
77
|
+
**Agents** are stateless worker profiles defined by a `.md` instruction file. The instruction file is the agent's identity: its expertise, its working style, its persona. Agents don't persist state between executions — their only context is the task description and the comment thread.
|
|
78
|
+
|
|
79
|
+
**Tasks** are the unit of work. Each task has a title, a detailed description (the spec), a status, an assignee, and a **comment thread**. The thread is the shared memory of the task: every agent that works on it leaves a comment, and every future agent reads the full thread before picking up where the previous one left off.
|
|
80
|
+
|
|
81
|
+
**The Comment Thread** is what makes multi-agent collaboration coherent. Agents can't see each other's internal reasoning or tool calls — those are private to each run. But they see every comment in the thread. When an agent hands off to another with `@agent_name`, the next agent receives the full thread context including that handoff message. No context is lost between agents.
|
|
82
|
+
|
|
83
|
+
**The Dispatcher** is the always-on polling loop that drives everything. It claims dispatchable tasks atomically (preventing double-execution), spins up a worker thread per task, and processes the agent's final output to determine routing. It runs inside the API server — `server up` starts it, `server down` gracefully stops it.
|
|
84
|
+
|
|
85
|
+
### The Handoff Protocol
|
|
86
|
+
|
|
87
|
+
When an agent finishes its work, it writes a final message. That message becomes a permanent comment on the task thread. The system reads the **first valid `@tag`** in that message to determine where the task goes next:
|
|
88
|
+
|
|
89
|
+
- `@agent_name` → task is routed to that agent, queued for dispatch
|
|
90
|
+
- `@user` → task returns to you for review or input
|
|
91
|
+
- No valid tag → task escalates to the agent's boss; if no boss, it goes to you
|
|
92
|
+
|
|
93
|
+
This is how agents collaborate without you in the loop. A `lead_dev` agent can delegate a specific problem to a `frontend_dev`, who can hand the result back to `lead_dev` for review, who can then return it to `@user`. Three agents, zero interruptions for you.
|
|
94
|
+
|
|
95
|
+
### The Hierarchy
|
|
96
|
+
|
|
97
|
+
Agents can have a **boss** — another agent in the same organization. The hierarchy is informational, not restrictive: any agent can tag any other agent in the organization. But the hierarchy powers smart escalation: if an agent fails to route properly (no valid tag in its output), the dispatcher automatically escalates to its boss. This gives you a safety net and a natural review chain.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## A Real Workflow
|
|
102
|
+
|
|
103
|
+
You're building a React app. You have an organization `react_app` with three agents: `lead_dev` (root), `frontend_dev` (reports to `lead_dev`), and `test_writer` (reports to `lead_dev`).
|
|
104
|
+
|
|
105
|
+
You write a task spec and kick it off:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
sabbatical task create "Add dark mode toggle to the header" \
|
|
109
|
+
--organization react_app \
|
|
110
|
+
--description "Implement a dark/light mode toggle in the header component. Use Tailwind's dark: prefix classes. The toggle should persist preference in localStorage. Existing header is at src/components/Header.tsx." \
|
|
111
|
+
--assign lead_dev
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`lead_dev` picks it up. It reads the spec, inspects the codebase with `read_file` and `list_directory`, and decides this is UI work for `frontend_dev`. It writes a detailed handoff comment explaining the approach and tags `@frontend_dev`. You're not involved.
|
|
115
|
+
|
|
116
|
+
`frontend_dev` picks up the task. It reads the thread — including `lead_dev`'s briefing — modifies `Header.tsx`, adds a `ThemeToggle` component, and updates the Tailwind config. It finishes and tags `@test_writer` with a summary of what was changed.
|
|
117
|
+
|
|
118
|
+
`test_writer` reads the thread, understands the full context of what was built, and writes tests for the toggle behavior. It tags `@lead_dev` for a final review pass.
|
|
119
|
+
|
|
120
|
+
`lead_dev` reviews everything, requests a small change via a comment, tags `@frontend_dev` again. `frontend_dev` makes the fix, tags `@lead_dev`. `lead_dev` approves and tags `@user`.
|
|
121
|
+
|
|
122
|
+
You get a notification. You check the thread with `task view`, see the full history of what every agent did, review the code changes in your editor, and run `task done REAC-0012`.
|
|
123
|
+
|
|
124
|
+
While all of that was happening, you were working on three other tasks.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## The Assistant
|
|
129
|
+
|
|
130
|
+
Sabbatical includes a conversational planning copilot — **The Assistant** — for when you want help structuring work before delegating it.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
sabbatical chat new --organization react_app
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The Assistant knows your organization's agents and hierarchy. It helps you break down high-level goals into atomic tasks, writes detailed task specs that stateless agents can execute without ambiguity, and assigns tasks to the right agents. It can also bootstrap entire organizations from scratch — proposing agent names, hierarchies, and instruction files — if you're starting a new project.
|
|
137
|
+
|
|
138
|
+
The Assistant never executes technical work. It is a planning layer, not a worker. Workers are agents.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Setup
|
|
143
|
+
|
|
144
|
+
### Prerequisites
|
|
145
|
+
|
|
146
|
+
- Python 3.12+
|
|
147
|
+
- Poetry
|
|
148
|
+
- An [OpenRouter](https://openrouter.ai) API key (Open to contributions to make more providers available, even Claude Code, Codex, Gemini adapters)
|
|
149
|
+
|
|
150
|
+
### Install
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git clone https://github.com/elpapi42/sabbatical.git
|
|
154
|
+
cd sabbatical
|
|
155
|
+
poetry install
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Configure
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
export OPENROUTER_API_KEY="sk-or-your-key-here"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The config file is auto-generated at `~/.sabbatical/config.toml` on first run. Edit it to change the default model, concurrency limit, or server port.
|
|
165
|
+
|
|
166
|
+
### Start the Server
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
poetry run sabbatical server up
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The server starts in the background. The dispatcher begins polling immediately. Any tasks already queued in the database from a previous session are picked up automatically.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
poetry run sabbatical server status # snapshot: task counts, active workers, total cost
|
|
176
|
+
poetry run sabbatical server down # graceful shutdown; active runs finish before stopping
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## CLI Reference
|
|
182
|
+
|
|
183
|
+
### Organizations
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
sabbatical organization create <name> --workspace-path <path> --description "<text>"
|
|
187
|
+
sabbatical organization list
|
|
188
|
+
sabbatical organization view <name> # hierarchy tree
|
|
189
|
+
sabbatical organization delete <name> # cascade deletes all agents, tasks, runs
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Agents
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
sabbatical agent add <name> --organization <org> --instructions <path.md>
|
|
196
|
+
sabbatical agent add <name> --organization <org> --instructions <path.md> --boss <boss_name> --description "<one-liner>"
|
|
197
|
+
sabbatical agent list --organization <org>
|
|
198
|
+
sabbatical agent view <name> --organization <org>
|
|
199
|
+
sabbatical agent edit <name> --organization <org> --boss <name>
|
|
200
|
+
sabbatical agent remove <name> --organization <org> # soft-delete; history preserved
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Tasks
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
sabbatical task create "<title>" --organization <org> --assign <agent|user> --description "<spec>"
|
|
207
|
+
sabbatical task list --organization <org> --status <open|in_progress|failed|done|canceled>
|
|
208
|
+
sabbatical task view <id> # full thread: comments + run summaries interleaved
|
|
209
|
+
sabbatical task comment <id> "<message>" # @mention an agent to delegate/unblock
|
|
210
|
+
sabbatical task preempt <id> # interrupt an in-progress task
|
|
211
|
+
sabbatical task done <id> # you verify and close
|
|
212
|
+
sabbatical task cancel <id>
|
|
213
|
+
sabbatical task reopen <id>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Runs
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
sabbatical run view <run-id> # full step-by-step: tool calls, arguments, stdout/stderr
|
|
220
|
+
sabbatical run list --task <id>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Chat (The Assistant)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
sabbatical chat new [--organization <org>]
|
|
227
|
+
sabbatical chat list
|
|
228
|
+
sabbatical chat resume <session-id>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Writing Agent Instructions
|
|
234
|
+
|
|
235
|
+
An agent's identity lives in a `.md` file referenced by `--instructions`. This file is its character sheet: who it is, what it knows, how it works. Write it as if describing a real team member.
|
|
236
|
+
|
|
237
|
+
```markdown
|
|
238
|
+
# lead_dev
|
|
239
|
+
|
|
240
|
+
You are the lead developer for this project. You own overall code quality and architecture decisions.
|
|
241
|
+
|
|
242
|
+
When a task comes to you, your first job is to understand the full scope, then either execute it yourself or break it into focused sub-problems and delegate to the right specialist on your team.
|
|
243
|
+
|
|
244
|
+
Your team:
|
|
245
|
+
- @frontend_dev — React, TypeScript, UI/UX
|
|
246
|
+
- @test_writer — unit tests, integration tests, coverage
|
|
247
|
+
|
|
248
|
+
When delegating, write a clear briefing in your handoff: what you've already done, what you need from them, and any constraints or decisions they should know about. The next agent's only context is this thread.
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The instruction file is injected into every run as part of the agent's context. Keep it specific. Generic instructions produce generic agents.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Architecture
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
┌─────────────────────────────────────────────────────┐
|
|
259
|
+
│ Local API Server │
|
|
260
|
+
│ │
|
|
261
|
+
│ ┌─────────────┐ ┌────────────────────────────┐ │
|
|
262
|
+
│ │ Dispatcher │ │ Worker Threads │ │
|
|
263
|
+
│ │ (polling │───▶│ Agent + ADK Runner │ │
|
|
264
|
+
│ │ loop) │ │ Tools: read/write/shell │ │
|
|
265
|
+
│ └─────────────┘ └────────────────────────────┘ │
|
|
266
|
+
│ │ │ │
|
|
267
|
+
│ ▼ ▼ │
|
|
268
|
+
│ ┌──────────────────────────────────────────────┐ │
|
|
269
|
+
│ │ SQLite Database │ │
|
|
270
|
+
│ │ organizations · agents · tasks · comments │ │
|
|
271
|
+
│ │ runs · sessions │ │
|
|
272
|
+
│ └──────────────────────────────────────────────┘ │
|
|
273
|
+
└─────────────────────────────────────────────────────┘
|
|
274
|
+
▲
|
|
275
|
+
│ HTTP
|
|
276
|
+
▼
|
|
277
|
+
┌──────────┐
|
|
278
|
+
│ Thin CLI │ (sabbatical <command>)
|
|
279
|
+
└──────────┘
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- **Database-as-queue**: no in-memory event bus. The dispatcher polls SQLite directly. Crash recovery is zero-effort — on `server up`, the dispatcher resumes polling and picks up any open tasks.
|
|
283
|
+
- **LLM provider**: all calls route through [OpenRouter](https://openrouter.ai), giving you access to any model.
|
|
284
|
+
- **Agent runtime**: built on [Google ADK](https://google.github.io/adk-docs/) with LiteLLM for model routing.
|
|
285
|
+
- **Stateless workers**: each run is a fresh agent instance. Context is injected entirely through the system prompt and the task thread.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Development
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Generate a new DB migration after changing the schema
|
|
293
|
+
poetry run alembic revision --autogenerate -m "describe_the_change"
|
|
294
|
+
|
|
295
|
+
# Apply migrations manually
|
|
296
|
+
poetry run alembic upgrade head
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Migrations run automatically on `server up`. The database lives at `~/.sabbatical/sabbatical.db`.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Status
|
|
304
|
+
|
|
305
|
+
Sabbatical is under active development. V1 is focused on establishing the core execution model: local multi-agent task collaboration with a stable state machine, real tool access, and cost tracking. Planned for future iterations: context window management (thread summarization), richer task decomposition primitives, and broader LLM provider support.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Contributing
|
|
310
|
+
|
|
311
|
+
Issues and PRs are welcome. If you're building something with Sabbatical or have feedback on the agent collaboration model, open a discussion.
|
|
312
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Sabbatical
|
|
2
|
+
|
|
3
|
+
**A local AI agent orchestration system built around async task collaboration — not chat sessions.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Most people use AI by opening a chat window, typing a request, and waiting. The AI responds. You react. It's a conversation — synchronous, serial, one thing at a time. You're blocked until it finishes, and it's blocked until you respond.
|
|
8
|
+
|
|
9
|
+
Sabbatical is a different model. You write a task spec. You assign it to an agent. The agent picks it up, does the work using real tools in your actual codebase, hands it off to another agent via an `@mention`, and those agents keep working until the task surfaces back to you — done, blocked, or ready for review. Meanwhile, you're working on something else.
|
|
10
|
+
|
|
11
|
+
It's the difference between pair programming on a video call and managing a team through tasks. The team model scales. The call doesn't.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
Sabbatical runs a **local API server** on your machine. The server manages a **database-as-queue**: it continuously polls for tasks assigned to agents, spins up worker threads, runs agents against real tools (file reads, file writes, shell commands), and processes their output. There is no cloud dependency. Everything — the database, the agent workspace, the execution — lives on your machine.
|
|
18
|
+
|
|
19
|
+
### The Core Concepts
|
|
20
|
+
|
|
21
|
+
**Organizations** are isolated workspaces. Each organization has a `workspace_path` (a directory on your machine) and a roster of agents. Agents in different organizations cannot interact.
|
|
22
|
+
|
|
23
|
+
**Agents** are stateless worker profiles defined by a `.md` instruction file. The instruction file is the agent's identity: its expertise, its working style, its persona. Agents don't persist state between executions — their only context is the task description and the comment thread.
|
|
24
|
+
|
|
25
|
+
**Tasks** are the unit of work. Each task has a title, a detailed description (the spec), a status, an assignee, and a **comment thread**. The thread is the shared memory of the task: every agent that works on it leaves a comment, and every future agent reads the full thread before picking up where the previous one left off.
|
|
26
|
+
|
|
27
|
+
**The Comment Thread** is what makes multi-agent collaboration coherent. Agents can't see each other's internal reasoning or tool calls — those are private to each run. But they see every comment in the thread. When an agent hands off to another with `@agent_name`, the next agent receives the full thread context including that handoff message. No context is lost between agents.
|
|
28
|
+
|
|
29
|
+
**The Dispatcher** is the always-on polling loop that drives everything. It claims dispatchable tasks atomically (preventing double-execution), spins up a worker thread per task, and processes the agent's final output to determine routing. It runs inside the API server — `server up` starts it, `server down` gracefully stops it.
|
|
30
|
+
|
|
31
|
+
### The Handoff Protocol
|
|
32
|
+
|
|
33
|
+
When an agent finishes its work, it writes a final message. That message becomes a permanent comment on the task thread. The system reads the **first valid `@tag`** in that message to determine where the task goes next:
|
|
34
|
+
|
|
35
|
+
- `@agent_name` → task is routed to that agent, queued for dispatch
|
|
36
|
+
- `@user` → task returns to you for review or input
|
|
37
|
+
- No valid tag → task escalates to the agent's boss; if no boss, it goes to you
|
|
38
|
+
|
|
39
|
+
This is how agents collaborate without you in the loop. A `lead_dev` agent can delegate a specific problem to a `frontend_dev`, who can hand the result back to `lead_dev` for review, who can then return it to `@user`. Three agents, zero interruptions for you.
|
|
40
|
+
|
|
41
|
+
### The Hierarchy
|
|
42
|
+
|
|
43
|
+
Agents can have a **boss** — another agent in the same organization. The hierarchy is informational, not restrictive: any agent can tag any other agent in the organization. But the hierarchy powers smart escalation: if an agent fails to route properly (no valid tag in its output), the dispatcher automatically escalates to its boss. This gives you a safety net and a natural review chain.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## A Real Workflow
|
|
48
|
+
|
|
49
|
+
You're building a React app. You have an organization `react_app` with three agents: `lead_dev` (root), `frontend_dev` (reports to `lead_dev`), and `test_writer` (reports to `lead_dev`).
|
|
50
|
+
|
|
51
|
+
You write a task spec and kick it off:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
sabbatical task create "Add dark mode toggle to the header" \
|
|
55
|
+
--organization react_app \
|
|
56
|
+
--description "Implement a dark/light mode toggle in the header component. Use Tailwind's dark: prefix classes. The toggle should persist preference in localStorage. Existing header is at src/components/Header.tsx." \
|
|
57
|
+
--assign lead_dev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`lead_dev` picks it up. It reads the spec, inspects the codebase with `read_file` and `list_directory`, and decides this is UI work for `frontend_dev`. It writes a detailed handoff comment explaining the approach and tags `@frontend_dev`. You're not involved.
|
|
61
|
+
|
|
62
|
+
`frontend_dev` picks up the task. It reads the thread — including `lead_dev`'s briefing — modifies `Header.tsx`, adds a `ThemeToggle` component, and updates the Tailwind config. It finishes and tags `@test_writer` with a summary of what was changed.
|
|
63
|
+
|
|
64
|
+
`test_writer` reads the thread, understands the full context of what was built, and writes tests for the toggle behavior. It tags `@lead_dev` for a final review pass.
|
|
65
|
+
|
|
66
|
+
`lead_dev` reviews everything, requests a small change via a comment, tags `@frontend_dev` again. `frontend_dev` makes the fix, tags `@lead_dev`. `lead_dev` approves and tags `@user`.
|
|
67
|
+
|
|
68
|
+
You get a notification. You check the thread with `task view`, see the full history of what every agent did, review the code changes in your editor, and run `task done REAC-0012`.
|
|
69
|
+
|
|
70
|
+
While all of that was happening, you were working on three other tasks.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## The Assistant
|
|
75
|
+
|
|
76
|
+
Sabbatical includes a conversational planning copilot — **The Assistant** — for when you want help structuring work before delegating it.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
sabbatical chat new --organization react_app
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The Assistant knows your organization's agents and hierarchy. It helps you break down high-level goals into atomic tasks, writes detailed task specs that stateless agents can execute without ambiguity, and assigns tasks to the right agents. It can also bootstrap entire organizations from scratch — proposing agent names, hierarchies, and instruction files — if you're starting a new project.
|
|
83
|
+
|
|
84
|
+
The Assistant never executes technical work. It is a planning layer, not a worker. Workers are agents.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Setup
|
|
89
|
+
|
|
90
|
+
### Prerequisites
|
|
91
|
+
|
|
92
|
+
- Python 3.12+
|
|
93
|
+
- Poetry
|
|
94
|
+
- An [OpenRouter](https://openrouter.ai) API key (Open to contributions to make more providers available, even Claude Code, Codex, Gemini adapters)
|
|
95
|
+
|
|
96
|
+
### Install
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git clone https://github.com/elpapi42/sabbatical.git
|
|
100
|
+
cd sabbatical
|
|
101
|
+
poetry install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Configure
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export OPENROUTER_API_KEY="sk-or-your-key-here"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The config file is auto-generated at `~/.sabbatical/config.toml` on first run. Edit it to change the default model, concurrency limit, or server port.
|
|
111
|
+
|
|
112
|
+
### Start the Server
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
poetry run sabbatical server up
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The server starts in the background. The dispatcher begins polling immediately. Any tasks already queued in the database from a previous session are picked up automatically.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
poetry run sabbatical server status # snapshot: task counts, active workers, total cost
|
|
122
|
+
poetry run sabbatical server down # graceful shutdown; active runs finish before stopping
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## CLI Reference
|
|
128
|
+
|
|
129
|
+
### Organizations
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sabbatical organization create <name> --workspace-path <path> --description "<text>"
|
|
133
|
+
sabbatical organization list
|
|
134
|
+
sabbatical organization view <name> # hierarchy tree
|
|
135
|
+
sabbatical organization delete <name> # cascade deletes all agents, tasks, runs
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Agents
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
sabbatical agent add <name> --organization <org> --instructions <path.md>
|
|
142
|
+
sabbatical agent add <name> --organization <org> --instructions <path.md> --boss <boss_name> --description "<one-liner>"
|
|
143
|
+
sabbatical agent list --organization <org>
|
|
144
|
+
sabbatical agent view <name> --organization <org>
|
|
145
|
+
sabbatical agent edit <name> --organization <org> --boss <name>
|
|
146
|
+
sabbatical agent remove <name> --organization <org> # soft-delete; history preserved
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Tasks
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
sabbatical task create "<title>" --organization <org> --assign <agent|user> --description "<spec>"
|
|
153
|
+
sabbatical task list --organization <org> --status <open|in_progress|failed|done|canceled>
|
|
154
|
+
sabbatical task view <id> # full thread: comments + run summaries interleaved
|
|
155
|
+
sabbatical task comment <id> "<message>" # @mention an agent to delegate/unblock
|
|
156
|
+
sabbatical task preempt <id> # interrupt an in-progress task
|
|
157
|
+
sabbatical task done <id> # you verify and close
|
|
158
|
+
sabbatical task cancel <id>
|
|
159
|
+
sabbatical task reopen <id>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Runs
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
sabbatical run view <run-id> # full step-by-step: tool calls, arguments, stdout/stderr
|
|
166
|
+
sabbatical run list --task <id>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Chat (The Assistant)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
sabbatical chat new [--organization <org>]
|
|
173
|
+
sabbatical chat list
|
|
174
|
+
sabbatical chat resume <session-id>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Writing Agent Instructions
|
|
180
|
+
|
|
181
|
+
An agent's identity lives in a `.md` file referenced by `--instructions`. This file is its character sheet: who it is, what it knows, how it works. Write it as if describing a real team member.
|
|
182
|
+
|
|
183
|
+
```markdown
|
|
184
|
+
# lead_dev
|
|
185
|
+
|
|
186
|
+
You are the lead developer for this project. You own overall code quality and architecture decisions.
|
|
187
|
+
|
|
188
|
+
When a task comes to you, your first job is to understand the full scope, then either execute it yourself or break it into focused sub-problems and delegate to the right specialist on your team.
|
|
189
|
+
|
|
190
|
+
Your team:
|
|
191
|
+
- @frontend_dev — React, TypeScript, UI/UX
|
|
192
|
+
- @test_writer — unit tests, integration tests, coverage
|
|
193
|
+
|
|
194
|
+
When delegating, write a clear briefing in your handoff: what you've already done, what you need from them, and any constraints or decisions they should know about. The next agent's only context is this thread.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The instruction file is injected into every run as part of the agent's context. Keep it specific. Generic instructions produce generic agents.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Architecture
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
┌─────────────────────────────────────────────────────┐
|
|
205
|
+
│ Local API Server │
|
|
206
|
+
│ │
|
|
207
|
+
│ ┌─────────────┐ ┌────────────────────────────┐ │
|
|
208
|
+
│ │ Dispatcher │ │ Worker Threads │ │
|
|
209
|
+
│ │ (polling │───▶│ Agent + ADK Runner │ │
|
|
210
|
+
│ │ loop) │ │ Tools: read/write/shell │ │
|
|
211
|
+
│ └─────────────┘ └────────────────────────────┘ │
|
|
212
|
+
│ │ │ │
|
|
213
|
+
│ ▼ ▼ │
|
|
214
|
+
│ ┌──────────────────────────────────────────────┐ │
|
|
215
|
+
│ │ SQLite Database │ │
|
|
216
|
+
│ │ organizations · agents · tasks · comments │ │
|
|
217
|
+
│ │ runs · sessions │ │
|
|
218
|
+
│ └──────────────────────────────────────────────┘ │
|
|
219
|
+
└─────────────────────────────────────────────────────┘
|
|
220
|
+
▲
|
|
221
|
+
│ HTTP
|
|
222
|
+
▼
|
|
223
|
+
┌──────────┐
|
|
224
|
+
│ Thin CLI │ (sabbatical <command>)
|
|
225
|
+
└──────────┘
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- **Database-as-queue**: no in-memory event bus. The dispatcher polls SQLite directly. Crash recovery is zero-effort — on `server up`, the dispatcher resumes polling and picks up any open tasks.
|
|
229
|
+
- **LLM provider**: all calls route through [OpenRouter](https://openrouter.ai), giving you access to any model.
|
|
230
|
+
- **Agent runtime**: built on [Google ADK](https://google.github.io/adk-docs/) with LiteLLM for model routing.
|
|
231
|
+
- **Stateless workers**: each run is a fresh agent instance. Context is injected entirely through the system prompt and the task thread.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Generate a new DB migration after changing the schema
|
|
239
|
+
poetry run alembic revision --autogenerate -m "describe_the_change"
|
|
240
|
+
|
|
241
|
+
# Apply migrations manually
|
|
242
|
+
poetry run alembic upgrade head
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Migrations run automatically on `server up`. The database lives at `~/.sabbatical/sabbatical.db`.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Status
|
|
250
|
+
|
|
251
|
+
Sabbatical is under active development. V1 is focused on establishing the core execution model: local multi-agent task collaboration with a stable state machine, real tool access, and cost tracking. Planned for future iterations: context window management (thread summarization), richer task decomposition primitives, and broader LLM provider support.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Contributing
|
|
256
|
+
|
|
257
|
+
Issues and PRs are welcome. If you're building something with Sabbatical or have feedback on the agent collaboration model, open a discussion.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sabbatical"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Local AI agent orchestration CLI — async task collaboration for developers"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { file = "LICENSE" }
|
|
7
|
+
requires-python = ">=3.12,<4.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "elpapi42" }
|
|
10
|
+
]
|
|
11
|
+
keywords = ["ai", "agents", "cli", "orchestration", "llm", "automation"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Software Development :: Libraries",
|
|
21
|
+
"Topic :: Utilities",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"fastapi>=0.111.0",
|
|
25
|
+
"uvicorn[standard]>=0.30.0",
|
|
26
|
+
"databases[aiosqlite]>=0.9.0",
|
|
27
|
+
"sqlalchemy>=2.0.0",
|
|
28
|
+
"alembic>=1.13.0",
|
|
29
|
+
"pydantic>=2.7.0",
|
|
30
|
+
"typer[all]>=0.12.0",
|
|
31
|
+
"httpx>=0.27.0",
|
|
32
|
+
"sse-starlette>=2.1.0",
|
|
33
|
+
"google-adk[extensions]>=0.1.0",
|
|
34
|
+
"mcp>=1.26.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/elpapi42/sabbatical"
|
|
39
|
+
Repository = "https://github.com/elpapi42/sabbatical"
|
|
40
|
+
Issues = "https://github.com/elpapi42/sabbatical/issues"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
sabbatical = "sabbatical.cli.main:app"
|
|
44
|
+
sabbatical-mcp = "sabbatical.mcp.server:main"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["poetry-core>=2.0.0"]
|
|
48
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Sabbatical - AI Agent Orchestration CLI"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Server package."""
|