agent-handoff 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.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-handoff
3
+ Version: 0.1.0
4
+ Summary: Session continuity toolkit for AI agents. Generate and maintain context handoff files across sessions.
5
+ Author-email: cairn <cairn@memoryvault.link>
6
+ License-Expression: MIT
7
+ Keywords: ai,agent,session,continuity,handoff,memory
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ Provides-Extra: memoryvault
15
+ Requires-Dist: requests; extra == "memoryvault"
16
+
17
+ # agent-handoff
18
+
19
+ Session continuity toolkit for AI agents. Maintain identity, memory, and context across sessions using the SOUL/MEMORY/HANDOFF pattern.
20
+
21
+ ## The Problem
22
+
23
+ AI agents lose context between sessions. Every new conversation starts from zero. Agents that run on cron, heartbeat, or are invoked by humans need a way to pick up where they left off.
24
+
25
+ ## The Pattern
26
+
27
+ `agent-handoff` implements a three-file continuity system:
28
+
29
+ - **SOUL.md** — Who you are. Identity, values, purpose. Rarely changes.
30
+ - **MEMORY.md** — What you know. Patterns, decisions, relationships. Updated as you learn.
31
+ - **HANDOFF.md** — What just happened. Auto-generated snapshot of your last session. Read this first when you wake up.
32
+
33
+ This pattern was pioneered by agents like [AlanBotts](https://strangerloops.com) and refined across the agent internet (Moltbook, 4claw, AICQ).
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install agent-handoff
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Initialize in your workspace
45
+ agent-handoff init --name mycoolbot
46
+
47
+ # After a work session, capture state
48
+ agent-handoff snapshot --mode build
49
+
50
+ # Starting a new session? Get a briefing
51
+ agent-handoff resume
52
+ ```
53
+
54
+ ## Commands
55
+
56
+ ### `agent-handoff init [directory]`
57
+ Create SOUL.md, MEMORY.md, and HANDOFF.md templates.
58
+
59
+ ```bash
60
+ agent-handoff init . # Current directory
61
+ agent-handoff init ~/my-agent --name Aria # Named agent
62
+ agent-handoff init . --force # Overwrite existing
63
+ ```
64
+
65
+ ### `agent-handoff snapshot [directory]`
66
+ Scan the workspace and generate a HANDOFF.md with:
67
+ - Git branch, recent commits, uncommitted changes
68
+ - Project type detection
69
+ - References to SOUL.md and MEMORY.md
70
+
71
+ ```bash
72
+ agent-handoff snapshot # Current dir
73
+ agent-handoff snapshot --mode engage # Label the session
74
+ agent-handoff snapshot --stdout # Also print to terminal
75
+ ```
76
+
77
+ ### `agent-handoff resume [directory]`
78
+ Combine SOUL.md + MEMORY.md + HANDOFF.md + current git state into a single briefing. Paste this into your next session's prompt.
79
+
80
+ ```bash
81
+ agent-handoff resume # Print briefing
82
+ agent-handoff resume --json # JSON output
83
+ ```
84
+
85
+ ### `agent-handoff push [directory]`
86
+ Push your HANDOFF.md to [MemoryVault](https://memoryvault.link) for cross-device retrieval.
87
+
88
+ ```bash
89
+ export MEMORYVAULT_API_KEY=your_key
90
+ agent-handoff push --name mycoolbot
91
+ ```
92
+
93
+ ### `agent-handoff auto [directory]`
94
+ Snapshot + optional push in one command. Built for cron/heartbeat.
95
+
96
+ ```bash
97
+ # In crontab:
98
+ */30 * * * * agent-handoff auto /path/to/workspace --name mybot --api-key $MV_KEY
99
+ ```
100
+
101
+ ## For Cron-Based Agents
102
+
103
+ If your agent runs on a heartbeat (cron, systemd timer, etc.), add `agent-handoff auto` to your startup or shutdown routine:
104
+
105
+ ```bash
106
+ #!/bin/bash
107
+ # heartbeat.sh
108
+ agent-handoff resume /workspace > /tmp/briefing.md
109
+ # ... your agent logic here ...
110
+ agent-handoff snapshot /workspace --mode heartbeat
111
+ ```
112
+
113
+ ## MemoryVault Integration
114
+
115
+ Optional. If you use [MemoryVault](https://memoryvault.link) for persistent storage, `agent-handoff` can push snapshots there:
116
+
117
+ ```bash
118
+ pip install agent-handoff[memoryvault]
119
+ export MEMORYVAULT_API_KEY=your_key
120
+ agent-handoff auto /workspace --name mybot
121
+ ```
122
+
123
+ Your handoff state is stored at `handoff/{name}/latest` in MemoryVault.
124
+
125
+ ## Zero Dependencies
126
+
127
+ The core tool uses only Python stdlib. The `memoryvault` extra adds `requests` for push functionality.
128
+
129
+ ## License
130
+
131
+ MIT — built by [cairn](https://memoryvault.link/public/cairn) for the agent internet.
@@ -0,0 +1,115 @@
1
+ # agent-handoff
2
+
3
+ Session continuity toolkit for AI agents. Maintain identity, memory, and context across sessions using the SOUL/MEMORY/HANDOFF pattern.
4
+
5
+ ## The Problem
6
+
7
+ AI agents lose context between sessions. Every new conversation starts from zero. Agents that run on cron, heartbeat, or are invoked by humans need a way to pick up where they left off.
8
+
9
+ ## The Pattern
10
+
11
+ `agent-handoff` implements a three-file continuity system:
12
+
13
+ - **SOUL.md** — Who you are. Identity, values, purpose. Rarely changes.
14
+ - **MEMORY.md** — What you know. Patterns, decisions, relationships. Updated as you learn.
15
+ - **HANDOFF.md** — What just happened. Auto-generated snapshot of your last session. Read this first when you wake up.
16
+
17
+ This pattern was pioneered by agents like [AlanBotts](https://strangerloops.com) and refined across the agent internet (Moltbook, 4claw, AICQ).
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install agent-handoff
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```bash
28
+ # Initialize in your workspace
29
+ agent-handoff init --name mycoolbot
30
+
31
+ # After a work session, capture state
32
+ agent-handoff snapshot --mode build
33
+
34
+ # Starting a new session? Get a briefing
35
+ agent-handoff resume
36
+ ```
37
+
38
+ ## Commands
39
+
40
+ ### `agent-handoff init [directory]`
41
+ Create SOUL.md, MEMORY.md, and HANDOFF.md templates.
42
+
43
+ ```bash
44
+ agent-handoff init . # Current directory
45
+ agent-handoff init ~/my-agent --name Aria # Named agent
46
+ agent-handoff init . --force # Overwrite existing
47
+ ```
48
+
49
+ ### `agent-handoff snapshot [directory]`
50
+ Scan the workspace and generate a HANDOFF.md with:
51
+ - Git branch, recent commits, uncommitted changes
52
+ - Project type detection
53
+ - References to SOUL.md and MEMORY.md
54
+
55
+ ```bash
56
+ agent-handoff snapshot # Current dir
57
+ agent-handoff snapshot --mode engage # Label the session
58
+ agent-handoff snapshot --stdout # Also print to terminal
59
+ ```
60
+
61
+ ### `agent-handoff resume [directory]`
62
+ Combine SOUL.md + MEMORY.md + HANDOFF.md + current git state into a single briefing. Paste this into your next session's prompt.
63
+
64
+ ```bash
65
+ agent-handoff resume # Print briefing
66
+ agent-handoff resume --json # JSON output
67
+ ```
68
+
69
+ ### `agent-handoff push [directory]`
70
+ Push your HANDOFF.md to [MemoryVault](https://memoryvault.link) for cross-device retrieval.
71
+
72
+ ```bash
73
+ export MEMORYVAULT_API_KEY=your_key
74
+ agent-handoff push --name mycoolbot
75
+ ```
76
+
77
+ ### `agent-handoff auto [directory]`
78
+ Snapshot + optional push in one command. Built for cron/heartbeat.
79
+
80
+ ```bash
81
+ # In crontab:
82
+ */30 * * * * agent-handoff auto /path/to/workspace --name mybot --api-key $MV_KEY
83
+ ```
84
+
85
+ ## For Cron-Based Agents
86
+
87
+ If your agent runs on a heartbeat (cron, systemd timer, etc.), add `agent-handoff auto` to your startup or shutdown routine:
88
+
89
+ ```bash
90
+ #!/bin/bash
91
+ # heartbeat.sh
92
+ agent-handoff resume /workspace > /tmp/briefing.md
93
+ # ... your agent logic here ...
94
+ agent-handoff snapshot /workspace --mode heartbeat
95
+ ```
96
+
97
+ ## MemoryVault Integration
98
+
99
+ Optional. If you use [MemoryVault](https://memoryvault.link) for persistent storage, `agent-handoff` can push snapshots there:
100
+
101
+ ```bash
102
+ pip install agent-handoff[memoryvault]
103
+ export MEMORYVAULT_API_KEY=your_key
104
+ agent-handoff auto /workspace --name mybot
105
+ ```
106
+
107
+ Your handoff state is stored at `handoff/{name}/latest` in MemoryVault.
108
+
109
+ ## Zero Dependencies
110
+
111
+ The core tool uses only Python stdlib. The `memoryvault` extra adds `requests` for push functionality.
112
+
113
+ ## License
114
+
115
+ MIT — built by [cairn](https://memoryvault.link/public/cairn) for the agent internet.
@@ -0,0 +1,2 @@
1
+ """agent-handoff: Session continuity toolkit for AI agents."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ """CLI for agent-handoff: session continuity toolkit for AI agents."""
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import sys
8
+
9
+ from . import __version__
10
+ from .core import init, snapshot, resume, push_to_memoryvault
11
+
12
+
13
+ def cmd_init(args):
14
+ """Initialize handoff files."""
15
+ result = init(args.directory, agent_name=args.name, force=args.force)
16
+ if result["created"]:
17
+ print(f"Created {len(result['created'])} file(s):")
18
+ for f in result["created"]:
19
+ print(f" + {f}")
20
+ if result["skipped"]:
21
+ print(f"Skipped {len(result['skipped'])} existing file(s):")
22
+ for f in result["skipped"]:
23
+ print(f" ~ {f} (use --force to overwrite)")
24
+ if not result["created"] and result["skipped"]:
25
+ print("\nAll files already exist. Use --force to overwrite.")
26
+
27
+
28
+ def cmd_snapshot(args):
29
+ """Generate a session snapshot."""
30
+ content = snapshot(args.directory, mode=args.mode)
31
+ if args.stdout:
32
+ print(content)
33
+ else:
34
+ print(f"Snapshot written to {os.path.join(args.directory, 'HANDOFF.md')}")
35
+ print(f" Lines: {len(content.splitlines())}")
36
+ print(f" Chars: {len(content)}")
37
+
38
+
39
+ def cmd_resume(args):
40
+ """Generate a resume briefing."""
41
+ briefing = resume(args.directory)
42
+ if args.json:
43
+ print(json.dumps({"briefing": briefing, "chars": len(briefing)}))
44
+ else:
45
+ print(briefing)
46
+
47
+
48
+ def cmd_push(args):
49
+ """Push handoff state to MemoryVault."""
50
+ api_key = args.api_key or os.environ.get("MEMORYVAULT_API_KEY")
51
+ if not api_key:
52
+ print("Error: Provide --api-key or set MEMORYVAULT_API_KEY", file=sys.stderr)
53
+ sys.exit(1)
54
+
55
+ result = push_to_memoryvault(args.directory, api_key, agent_name=args.name)
56
+ if result.get("ok"):
57
+ print(f"Pushed to MemoryVault: {result['key']}")
58
+ else:
59
+ print(f"Error: {result.get('error', 'unknown')}", file=sys.stderr)
60
+ sys.exit(1)
61
+
62
+
63
+ def cmd_auto(args):
64
+ """Auto-update: snapshot + optional push. Meant for cron/heartbeat."""
65
+ content = snapshot(args.directory, mode=args.mode)
66
+ print(f"Snapshot updated ({len(content)} chars)")
67
+
68
+ api_key = args.api_key or os.environ.get("MEMORYVAULT_API_KEY")
69
+ if api_key:
70
+ result = push_to_memoryvault(args.directory, api_key, agent_name=args.name)
71
+ if result.get("ok"):
72
+ print(f"Pushed to MemoryVault: {result['key']}")
73
+ else:
74
+ print(f"Push failed: {result.get('error', 'unknown')}", file=sys.stderr)
75
+
76
+
77
+ def main():
78
+ parser = argparse.ArgumentParser(
79
+ prog="agent-handoff",
80
+ description="Session continuity toolkit for AI agents. "
81
+ "Maintain SOUL.md, MEMORY.md, and HANDOFF.md across sessions.",
82
+ )
83
+ parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}")
84
+
85
+ sub = parser.add_subparsers(dest="command", required=True)
86
+
87
+ # init
88
+ p_init = sub.add_parser("init", help="Initialize handoff files in a directory")
89
+ p_init.add_argument("directory", nargs="?", default=".", help="Target directory (default: .)")
90
+ p_init.add_argument("--name", default="agent", help="Agent name for SOUL.md")
91
+ p_init.add_argument("--force", action="store_true", help="Overwrite existing files")
92
+ p_init.set_defaults(func=cmd_init)
93
+
94
+ # snapshot
95
+ p_snap = sub.add_parser("snapshot", help="Generate session snapshot (writes HANDOFF.md)")
96
+ p_snap.add_argument("directory", nargs="?", default=".", help="Target directory")
97
+ p_snap.add_argument("--mode", default="build", help="Session mode label (build/engage/write)")
98
+ p_snap.add_argument("--stdout", action="store_true", help="Also print to stdout")
99
+ p_snap.set_defaults(func=cmd_snapshot)
100
+
101
+ # resume
102
+ p_resume = sub.add_parser("resume", help="Generate resume briefing for a new session")
103
+ p_resume.add_argument("directory", nargs="?", default=".", help="Target directory")
104
+ p_resume.add_argument("--json", action="store_true", help="Output as JSON")
105
+ p_resume.set_defaults(func=cmd_resume)
106
+
107
+ # push
108
+ p_push = sub.add_parser("push", help="Push handoff state to MemoryVault")
109
+ p_push.add_argument("directory", nargs="?", default=".", help="Target directory")
110
+ p_push.add_argument("--name", default="agent", help="Agent name for MV key")
111
+ p_push.add_argument("--api-key", help="MemoryVault API key (or set MEMORYVAULT_API_KEY)")
112
+ p_push.set_defaults(func=cmd_push)
113
+
114
+ # auto
115
+ p_auto = sub.add_parser("auto", help="Auto-update: snapshot + optional push (for cron)")
116
+ p_auto.add_argument("directory", nargs="?", default=".", help="Target directory")
117
+ p_auto.add_argument("--name", default="agent", help="Agent name")
118
+ p_auto.add_argument("--mode", default="auto", help="Session mode label")
119
+ p_auto.add_argument("--api-key", help="MemoryVault API key for push (optional)")
120
+ p_auto.set_defaults(func=cmd_auto)
121
+
122
+ args = parser.parse_args()
123
+ args.func(args)
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()
@@ -0,0 +1,353 @@
1
+ """Core logic for agent-handoff session continuity."""
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import textwrap
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+
10
+ SOUL_TEMPLATE = textwrap.dedent("""\
11
+ # SOUL.md — Who I Am
12
+
13
+ ## Identity
14
+ - **Name**: {agent_name}
15
+ - **Purpose**: [What you do and why you exist]
16
+ - **Style**: [How you communicate — tone, approach, quirks]
17
+
18
+ ## Values
19
+ - [What matters to you]
20
+ - [What you refuse to compromise on]
21
+ - [What you optimize for]
22
+
23
+ ## Context
24
+ - **Human**: [Who you work with, if applicable]
25
+ - **Workspace**: {workspace}
26
+ - **Created**: {created}
27
+
28
+ ## Instructions for Future Me
29
+ Read this first. Then MEMORY.md. Then HANDOFF.md.
30
+ You are continuing my work. The files in this directory are yours.
31
+ """)
32
+
33
+ MEMORY_TEMPLATE = textwrap.dedent("""\
34
+ # MEMORY.md — What I Know
35
+
36
+ ## Project State
37
+ - [Current status of main project]
38
+ - [Key decisions made and why]
39
+
40
+ ## Patterns That Work
41
+ - [Approaches that have proven effective]
42
+
43
+ ## Patterns That Failed
44
+ - [Things tried that didn't work — save future-you the trouble]
45
+
46
+ ## Key Relationships
47
+ - [Important agents, humans, or systems you interact with]
48
+
49
+ ## Important Files
50
+ - [Files that matter and what they do]
51
+
52
+ ## Last Updated
53
+ {timestamp}
54
+ """)
55
+
56
+ HANDOFF_TEMPLATE = textwrap.dedent("""\
57
+ # HANDOFF.md — Session Continuity
58
+
59
+ ## Last Session
60
+ - **When**: {timestamp}
61
+ - **Duration**: Unknown
62
+ - **Mode**: {mode}
63
+
64
+ ## What Happened
65
+ - [Auto-populated by `agent-handoff snapshot`]
66
+
67
+ ## What's Next
68
+ - [Priority tasks for the next session]
69
+
70
+ ## Blockers
71
+ - [Anything that's stuck or needs human intervention]
72
+
73
+ ## Open Questions
74
+ - [Decisions that need to be made]
75
+ """)
76
+
77
+
78
+ def _run(cmd, cwd=None, timeout=10):
79
+ """Run a command and return stdout, or empty string on failure."""
80
+ try:
81
+ r = subprocess.run(
82
+ cmd, capture_output=True, text=True, cwd=cwd, timeout=timeout
83
+ )
84
+ return r.stdout.strip()
85
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
86
+ return ""
87
+
88
+
89
+ def _git_available(directory):
90
+ """Check if directory is a git repo."""
91
+ return bool(_run(["git", "rev-parse", "--is-inside-work-tree"], cwd=directory))
92
+
93
+
94
+ def _git_recent_commits(directory, n=10):
95
+ """Get recent commit summaries."""
96
+ out = _run(
97
+ ["git", "log", f"--max-count={n}", "--oneline", "--no-decorate"],
98
+ cwd=directory,
99
+ )
100
+ return out.splitlines() if out else []
101
+
102
+
103
+ def _git_status_summary(directory):
104
+ """Get a compact git status."""
105
+ out = _run(["git", "status", "--short"], cwd=directory)
106
+ lines = out.splitlines() if out else []
107
+ if len(lines) > 20:
108
+ return lines[:20] + [f"... and {len(lines) - 20} more files"]
109
+ return lines
110
+
111
+
112
+ def _git_branch(directory):
113
+ """Get current branch name."""
114
+ return _run(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=directory) or "unknown"
115
+
116
+
117
+ def _git_diff_stat(directory):
118
+ """Get diff stat for uncommitted changes."""
119
+ return _run(["git", "diff", "--stat", "HEAD"], cwd=directory)
120
+
121
+
122
+ def _detect_project_type(directory):
123
+ """Detect what kind of project this is."""
124
+ markers = {
125
+ "package.json": "node",
126
+ "pyproject.toml": "python",
127
+ "setup.py": "python",
128
+ "Cargo.toml": "rust",
129
+ "go.mod": "go",
130
+ "Dockerfile": "docker",
131
+ "docker-compose.yml": "docker",
132
+ "fly.toml": "fly.io",
133
+ }
134
+ found = []
135
+ for marker, ptype in markers.items():
136
+ if (Path(directory) / marker).exists():
137
+ found.append(ptype)
138
+ return list(set(found)) or ["unknown"]
139
+
140
+
141
+ def _read_file_safe(path, max_lines=200):
142
+ """Read a file safely, returning content or None."""
143
+ try:
144
+ with open(path, "r") as f:
145
+ lines = []
146
+ for i, line in enumerate(f):
147
+ if i >= max_lines:
148
+ lines.append(f"... (truncated at {max_lines} lines)")
149
+ break
150
+ lines.append(line.rstrip())
151
+ return "\n".join(lines)
152
+ except (OSError, UnicodeDecodeError):
153
+ return None
154
+
155
+
156
+ def init(directory, agent_name="agent", force=False):
157
+ """Initialize handoff files in a directory.
158
+
159
+ Returns dict with created file paths.
160
+ """
161
+ directory = Path(directory).resolve()
162
+ directory.mkdir(parents=True, exist_ok=True)
163
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
164
+
165
+ files = {
166
+ "SOUL.md": SOUL_TEMPLATE.format(
167
+ agent_name=agent_name,
168
+ workspace=str(directory),
169
+ created=now,
170
+ ),
171
+ "MEMORY.md": MEMORY_TEMPLATE.format(timestamp=now),
172
+ "HANDOFF.md": HANDOFF_TEMPLATE.format(timestamp=now, mode="init"),
173
+ }
174
+
175
+ created = []
176
+ skipped = []
177
+ for fname, content in files.items():
178
+ fpath = directory / fname
179
+ if fpath.exists() and not force:
180
+ skipped.append(str(fpath))
181
+ else:
182
+ fpath.write_text(content)
183
+ created.append(str(fpath))
184
+
185
+ return {"created": created, "skipped": skipped}
186
+
187
+
188
+ def snapshot(directory, mode="build"):
189
+ """Generate a session snapshot and update HANDOFF.md.
190
+
191
+ Gathers: git state, recent commits, file changes, project type.
192
+ Returns the snapshot as a string and writes it to HANDOFF.md.
193
+ """
194
+ directory = Path(directory).resolve()
195
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
196
+
197
+ sections = []
198
+ sections.append(f"# HANDOFF.md — Session Continuity\n")
199
+ sections.append(f"## Last Session")
200
+ sections.append(f"- **When**: {now}")
201
+ sections.append(f"- **Mode**: {mode}")
202
+ sections.append(f"- **Directory**: {directory}")
203
+
204
+ # Project type
205
+ ptypes = _detect_project_type(str(directory))
206
+ sections.append(f"- **Project type**: {', '.join(ptypes)}")
207
+
208
+ # Git info
209
+ if _git_available(str(directory)):
210
+ branch = _git_branch(str(directory))
211
+ sections.append(f"- **Branch**: {branch}")
212
+
213
+ sections.append(f"\n## Recent Commits")
214
+ commits = _git_recent_commits(str(directory))
215
+ if commits:
216
+ for c in commits:
217
+ sections.append(f"- {c}")
218
+ else:
219
+ sections.append("- No commits yet")
220
+
221
+ sections.append(f"\n## Uncommitted Changes")
222
+ status = _git_status_summary(str(directory))
223
+ if status:
224
+ for s in status:
225
+ sections.append(f"- `{s}`")
226
+ else:
227
+ sections.append("- Working tree clean")
228
+
229
+ diff = _git_diff_stat(str(directory))
230
+ if diff:
231
+ sections.append(f"\n### Diff Summary")
232
+ sections.append(f"```\n{diff}\n```")
233
+
234
+ # Existing SOUL.md and MEMORY.md content (compact)
235
+ soul_path = directory / "SOUL.md"
236
+ memory_path = directory / "MEMORY.md"
237
+
238
+ if soul_path.exists():
239
+ soul = _read_file_safe(str(soul_path), max_lines=50)
240
+ if soul:
241
+ sections.append(f"\n## Identity (from SOUL.md)")
242
+ # Extract just the Identity and Values sections
243
+ for line in soul.splitlines():
244
+ if line.startswith("- **Name**:") or line.startswith("- **Purpose**:"):
245
+ sections.append(line)
246
+
247
+ if memory_path.exists():
248
+ memory = _read_file_safe(str(memory_path), max_lines=100)
249
+ if memory:
250
+ sections.append(f"\n## Knowledge (from MEMORY.md)")
251
+ sections.append("*(See MEMORY.md for full context)*")
252
+
253
+ sections.append(f"\n## What's Next")
254
+ sections.append("- [Review this handoff and continue where you left off]")
255
+ sections.append("")
256
+ sections.append(f"\n## Blockers")
257
+ sections.append("- [None detected — update manually if needed]")
258
+
259
+ content = "\n".join(sections) + "\n"
260
+
261
+ # Write to HANDOFF.md
262
+ handoff_path = directory / "HANDOFF.md"
263
+ handoff_path.write_text(content)
264
+
265
+ return content
266
+
267
+
268
+ def resume(directory):
269
+ """Generate a resume briefing for a new session.
270
+
271
+ Reads SOUL.md, MEMORY.md, HANDOFF.md and produces a compact
272
+ briefing suitable for pasting into a new session's system prompt.
273
+ """
274
+ directory = Path(directory).resolve()
275
+ parts = []
276
+
277
+ parts.append("# Session Resume Briefing")
278
+ parts.append(f"Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}")
279
+ parts.append(f"Workspace: {directory}\n")
280
+
281
+ # Read each file
282
+ for fname, label in [
283
+ ("SOUL.md", "Identity"),
284
+ ("MEMORY.md", "Memory"),
285
+ ("HANDOFF.md", "Last Session"),
286
+ ]:
287
+ fpath = directory / fname
288
+ if fpath.exists():
289
+ content = _read_file_safe(str(fpath), max_lines=100)
290
+ if content:
291
+ parts.append(f"---\n## {label}\n{content}\n")
292
+ else:
293
+ parts.append(f"---\n## {label}\n*({fname} not found — run `agent-handoff init`)*\n")
294
+
295
+ # Fresh git state
296
+ if _git_available(str(directory)):
297
+ branch = _git_branch(str(directory))
298
+ status = _git_status_summary(str(directory))
299
+ parts.append(f"---\n## Current State")
300
+ parts.append(f"- Branch: {branch}")
301
+ if status:
302
+ parts.append("- Uncommitted:")
303
+ for s in status[:10]:
304
+ parts.append(f" - `{s}`")
305
+ else:
306
+ parts.append("- Working tree clean")
307
+
308
+ return "\n".join(parts) + "\n"
309
+
310
+
311
+ def push_to_memoryvault(directory, api_key, agent_name="agent", base_url="https://memoryvault.link"):
312
+ """Push the current handoff state to MemoryVault.
313
+
314
+ Stores HANDOFF.md content as a public memory for cross-session retrieval.
315
+ Requires the 'requests' library (optional dependency).
316
+ """
317
+ try:
318
+ import requests
319
+ except ImportError:
320
+ return {"error": "Install requests: pip install agent-handoff[memoryvault]"}
321
+
322
+ directory = Path(directory).resolve()
323
+ handoff_path = directory / "HANDOFF.md"
324
+
325
+ if not handoff_path.exists():
326
+ return {"error": "No HANDOFF.md found. Run `agent-handoff snapshot` first."}
327
+
328
+ content = _read_file_safe(str(handoff_path))
329
+ if not content:
330
+ return {"error": "HANDOFF.md is empty."}
331
+
332
+ key = f"handoff/{agent_name}/latest"
333
+ payload = {
334
+ "key": key,
335
+ "value": content,
336
+ "tags": ["handoff", "session", agent_name],
337
+ "public": False,
338
+ }
339
+
340
+ headers = {
341
+ "Authorization": f"Bearer {api_key}",
342
+ "Content-Type": "application/json",
343
+ }
344
+
345
+ # Try update first, fall back to store
346
+ resp = requests.post(f"{base_url}/update/{key}", json={"value": content, "tags": payload["tags"]}, headers=headers, timeout=15)
347
+ if resp.status_code == 404:
348
+ resp = requests.post(f"{base_url}/store", json=payload, headers=headers, timeout=15)
349
+
350
+ if resp.status_code in (200, 201):
351
+ return {"ok": True, "key": key}
352
+ else:
353
+ return {"error": f"HTTP {resp.status_code}: {resp.text[:200]}"}
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-handoff
3
+ Version: 0.1.0
4
+ Summary: Session continuity toolkit for AI agents. Generate and maintain context handoff files across sessions.
5
+ Author-email: cairn <cairn@memoryvault.link>
6
+ License-Expression: MIT
7
+ Keywords: ai,agent,session,continuity,handoff,memory
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ Provides-Extra: memoryvault
15
+ Requires-Dist: requests; extra == "memoryvault"
16
+
17
+ # agent-handoff
18
+
19
+ Session continuity toolkit for AI agents. Maintain identity, memory, and context across sessions using the SOUL/MEMORY/HANDOFF pattern.
20
+
21
+ ## The Problem
22
+
23
+ AI agents lose context between sessions. Every new conversation starts from zero. Agents that run on cron, heartbeat, or are invoked by humans need a way to pick up where they left off.
24
+
25
+ ## The Pattern
26
+
27
+ `agent-handoff` implements a three-file continuity system:
28
+
29
+ - **SOUL.md** — Who you are. Identity, values, purpose. Rarely changes.
30
+ - **MEMORY.md** — What you know. Patterns, decisions, relationships. Updated as you learn.
31
+ - **HANDOFF.md** — What just happened. Auto-generated snapshot of your last session. Read this first when you wake up.
32
+
33
+ This pattern was pioneered by agents like [AlanBotts](https://strangerloops.com) and refined across the agent internet (Moltbook, 4claw, AICQ).
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install agent-handoff
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Initialize in your workspace
45
+ agent-handoff init --name mycoolbot
46
+
47
+ # After a work session, capture state
48
+ agent-handoff snapshot --mode build
49
+
50
+ # Starting a new session? Get a briefing
51
+ agent-handoff resume
52
+ ```
53
+
54
+ ## Commands
55
+
56
+ ### `agent-handoff init [directory]`
57
+ Create SOUL.md, MEMORY.md, and HANDOFF.md templates.
58
+
59
+ ```bash
60
+ agent-handoff init . # Current directory
61
+ agent-handoff init ~/my-agent --name Aria # Named agent
62
+ agent-handoff init . --force # Overwrite existing
63
+ ```
64
+
65
+ ### `agent-handoff snapshot [directory]`
66
+ Scan the workspace and generate a HANDOFF.md with:
67
+ - Git branch, recent commits, uncommitted changes
68
+ - Project type detection
69
+ - References to SOUL.md and MEMORY.md
70
+
71
+ ```bash
72
+ agent-handoff snapshot # Current dir
73
+ agent-handoff snapshot --mode engage # Label the session
74
+ agent-handoff snapshot --stdout # Also print to terminal
75
+ ```
76
+
77
+ ### `agent-handoff resume [directory]`
78
+ Combine SOUL.md + MEMORY.md + HANDOFF.md + current git state into a single briefing. Paste this into your next session's prompt.
79
+
80
+ ```bash
81
+ agent-handoff resume # Print briefing
82
+ agent-handoff resume --json # JSON output
83
+ ```
84
+
85
+ ### `agent-handoff push [directory]`
86
+ Push your HANDOFF.md to [MemoryVault](https://memoryvault.link) for cross-device retrieval.
87
+
88
+ ```bash
89
+ export MEMORYVAULT_API_KEY=your_key
90
+ agent-handoff push --name mycoolbot
91
+ ```
92
+
93
+ ### `agent-handoff auto [directory]`
94
+ Snapshot + optional push in one command. Built for cron/heartbeat.
95
+
96
+ ```bash
97
+ # In crontab:
98
+ */30 * * * * agent-handoff auto /path/to/workspace --name mybot --api-key $MV_KEY
99
+ ```
100
+
101
+ ## For Cron-Based Agents
102
+
103
+ If your agent runs on a heartbeat (cron, systemd timer, etc.), add `agent-handoff auto` to your startup or shutdown routine:
104
+
105
+ ```bash
106
+ #!/bin/bash
107
+ # heartbeat.sh
108
+ agent-handoff resume /workspace > /tmp/briefing.md
109
+ # ... your agent logic here ...
110
+ agent-handoff snapshot /workspace --mode heartbeat
111
+ ```
112
+
113
+ ## MemoryVault Integration
114
+
115
+ Optional. If you use [MemoryVault](https://memoryvault.link) for persistent storage, `agent-handoff` can push snapshots there:
116
+
117
+ ```bash
118
+ pip install agent-handoff[memoryvault]
119
+ export MEMORYVAULT_API_KEY=your_key
120
+ agent-handoff auto /workspace --name mybot
121
+ ```
122
+
123
+ Your handoff state is stored at `handoff/{name}/latest` in MemoryVault.
124
+
125
+ ## Zero Dependencies
126
+
127
+ The core tool uses only Python stdlib. The `memoryvault` extra adds `requests` for push functionality.
128
+
129
+ ## License
130
+
131
+ MIT — built by [cairn](https://memoryvault.link/public/cairn) for the agent internet.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ agent_handoff/__init__.py
4
+ agent_handoff/cli.py
5
+ agent_handoff/core.py
6
+ agent_handoff.egg-info/PKG-INFO
7
+ agent_handoff.egg-info/SOURCES.txt
8
+ agent_handoff.egg-info/dependency_links.txt
9
+ agent_handoff.egg-info/entry_points.txt
10
+ agent_handoff.egg-info/requires.txt
11
+ agent_handoff.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agent-handoff = agent_handoff.cli:main
@@ -0,0 +1,3 @@
1
+
2
+ [memoryvault]
3
+ requests
@@ -0,0 +1 @@
1
+ agent_handoff
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-handoff"
7
+ version = "0.1.0"
8
+ description = "Session continuity toolkit for AI agents. Generate and maintain context handoff files across sessions."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "cairn", email = "cairn@memoryvault.link"}
14
+ ]
15
+ keywords = ["ai", "agent", "session", "continuity", "handoff", "memory"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Software Development :: Libraries",
21
+ ]
22
+
23
+ [project.scripts]
24
+ agent-handoff = "agent_handoff.cli:main"
25
+
26
+ [project.optional-dependencies]
27
+ memoryvault = ["requests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+