diff --git a/docs/comparison.md b/docs/comparison.md new file mode 100644 index 0000000..0a430c5 --- /dev/null +++ b/docs/comparison.md @@ -0,0 +1,110 @@ +# org-workspace vs orgparse vs organice + +> A factual comparison for developers who work with org-mode files in Python. + +All three libraries solve different problems. This page helps you pick the right one. + +--- + +## TL;DR + +| Capability | orgparse | organice | org-workspace | +|-----------|---------|---------|---------------| +| Parse org files | ✅ | ✅ (via JS) | ✅ (via orgparse fork) | +| Write / mutate org files | ❌ | ✅ (in-browser) | ✅ | +| Python library | ✅ | ❌ (JS/TS) | ✅ | +| Multi-file workspace | ❌ | ❌ | ✅ | +| Atomic task claiming | ❌ | ❌ | ✅ | +| Concurrency primitives | ❌ | ❌ | ✅ | +| GTD query layer | ❌ | ❌ | ✅ | +| Dependency DAG (Plan) | ❌ | ❌ | ✅ | +| AI agent integration | ❌ | ❌ | ✅ | +| Round-trip safe serialization | ⚠️ partial | ✅ (JS) | ✅ | + +--- + +## orgparse + +[orgparse](https://github.com/karlicoss/orgparse) is a mature, well-tested Python parser for org-mode. It's the right choice if you want to **read** org files and don't need to write back. + +**Strengths:** +- Battle-tested and widely used +- Clean, Pythonic API for reading headings, properties, and text +- Good performance on large files + +**Limitations:** +- Read-only: no write support in the upstream library (PR #77 adds it but hasn't merged) +- No multi-file workspace concept +- No query layer for GTD patterns +- No concurrency support + +**When to use:** Reporting, analytics, read-only tooling over org files. + +org-workspace includes a vendored fork of orgparse with write support from PR #77. If your project already depends on orgparse for reading, org-workspace is a compatible upgrade path. + +--- + +## organice + +[organice](https://github.com/200ok-ch/organice) is a web application for editing org-mode files in a browser, with sync support for Dropbox, WebDAV, and Nextcloud. It's a frontend tool, not a Python library. + +**Strengths:** +- Clean mobile and desktop web interface +- Real org-mode support including agenda, TODO states, and tags +- Sync integrations with common file storage services + +**Limitations:** +- JavaScript/TypeScript only — no Python API +- Designed for human editing, not programmatic access +- No agent integration, task claiming, or batch operations + +**When to use:** You want a Dropbox-synced org agenda in a browser. + +--- + +## org-workspace + +org-workspace is a **Python library for AI agents and automation** that need to read, write, and reason over org-mode files. It was built to let autonomous agents safely coordinate over a shared task system without stepping on each other. + +**Strengths:** +- Full read/write Python API +- Multi-file workspace with dirty-file tracking +- Concurrency primitives for multi-agent coordination (TaskClaim, OptimisticLock, FileLock) +- GTD query layer (agenda, deadlines, overdue, next_action, ai_tasks) +- Dependency DAG with topological sort (Plan) +- Round-trip safe serialization: unmodified files are never rewritten +- GTD and nightshift state configurations out of the box +- Content-addressed ID generation with dedup detection + +**Limitations:** +- Newer project (v0.5.3, Beta) — fewer real-world deployments than orgparse +- No standalone CLI +- No browser or sync interface + +**When to use:** Building AI agents, automation pipelines, or developer tools that need to read and write org-mode task lists programmatically. + +--- + +## Feature deep-dive: write safety + +One reason to choose org-workspace over rolling your own orgparse wrapper: write safety is surprisingly hard. + +The naive approach — read → modify in-memory → serialize → write — routinely causes subtle bugs: property drawers getting duplicated, timestamps reformatted, tags reordered, or entire lines truncated on long values. These bugs don't fail loudly; they corrupt your files silently. + +org-workspace's serializer is designed around the round-trip invariant: a file that's loaded and immediately saved must produce byte-identical output for unchanged nodes. This is validated in the test suite on real org files. The `CatastrophicShrinkError` guard will refuse to write a file that's significantly smaller than the original, catching serializer regressions before they reach disk. + +--- + +## FAQ + +**Can I use org-workspace alongside orgparse?** +Yes. org-workspace vendors its own copy of orgparse internally, so there's no conflict. You can use the upstream orgparse for reading and org-workspace for writing if needed. + +**Does org-workspace work with Emacs's org-mode?** +Yes — org-workspace produces valid org-mode syntax. Files modified by org-workspace open correctly in Emacs without reformatting. + +**What Python versions are supported?** +Python 3.10–3.13. + +**Is it production-ready?** +It's at Beta stability (v0.5.3). The core API is stable; the advanced concurrency and Plan features are in use in Datacore's own autonomous agent pipeline. diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 0000000..7fcd873 --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,140 @@ +# org-workspace Quick Start + +> From install to your first AI-readable task list in under five minutes. + +## Install + +```bash +pip install org-workspace +``` + +## Your first five minutes + +Assume you have at least one org-mode file. If not, create a minimal one: + +``` +~/org/inbox.org: + +* TODO Write my first task + :PROPERTIES: + :ID: abc123 + :END: + This is what org-workspace can read and write. +``` + +Now open Python: + +```python +from pathlib import Path +from org_workspace import OrgWorkspace, Query + +# Point at your org directory (or a single file) +ws = OrgWorkspace(roots=[Path.home() / "org"]) + +# Find all tasks tagged for AI execution +q = Query(ws) +for task in q.ai_tasks(states=["TODO"]): + print(task.heading, task.tags) + +# Create a task programmatically +new_task = ws.create_node( + file=Path.home() / "org" / "inbox.org", + heading="Summarise my meeting notes", + state="TODO", + tags=["AI", "research"], + body="Use the attached notes in ~/org/notes/2026-07-meeting.org", +) + +# Save — only modified files are written, unchanged files are left alone +ws.save() +print("Task created:", new_task.id) +``` + +That's it. You've loaded a workspace, queried it, created a task, and saved safely. + +## What just happened + +**`OrgWorkspace`** loads and indexes all your org files. It tracks which files are dirty and guarantees round-trip safety — if you read a file and write it back unchanged, the bytes on disk don't change. + +**`Query`** is a stateless query layer on top of the workspace. `ai_tasks()` finds any heading tagged `:AI:` — the convention Datacore uses to hand work to autonomous agents overnight. + +**`create_node()`** inserts a new heading with a unique content-addressed ID. It will never silently overwrite an existing node. + +## The key primitives + +| Primitive | What it does | +|-----------|-------------| +| `OrgWorkspace` | Load, index, and safely write org files | +| `Query` | Read-only queries: agenda, deadlines, AI tasks, next action | +| `NodeView` | Stateless view of a single node; detects staleness automatically | +| `TaskClaim` | Atomic task claiming — prevents two agents running the same task | +| `OptimisticLock` | Hash-based conflict detection for concurrent writes | +| `Plan` | Parse `DEPENDS_ON` into a dependency DAG, topological sort | + +## Common patterns for AI agents + +### Claim a task before executing + +```python +from pathlib import Path +from org_workspace import OrgWorkspace, Query +from org_workspace.concurrency import TaskClaim + +ws = OrgWorkspace(roots=[Path.home() / "org"]) +q = Query(ws) +tc = TaskClaim(ws) + +for task in q.ai_tasks(states=["TODO"]): + if tc.claim(task, agent_id="my-agent"): + # Only one agent gets here; others skip this task + ws.transition(task, "EXECUTING", agent="my-agent") + ws.save() + # ... do work ... + ws.transition(task, "DONE", agent="my-agent") + tc.release(task, "my-agent") + ws.save() +``` + +### Find overdue tasks + +```python +overdue = q.overdue() +for task in overdue: + print(f"{task.heading} — deadline: {task.deadline}") +``` + +### Build a dependency plan + +```python +from org_workspace import Plan + +# Plan wraps a project subtree — find the root node first +roots = q.by_property("ID", "your-project-id") +if roots: + plan = Plan(roots[0], ws) + for task in plan.ready_tasks(): + print("Ready to run:", task.heading) + for task, blocker in plan.blocked_tasks(): + print("Blocked:", task.heading, "(waiting on:", blocker, ")") +``` + +## GTD states + +org-workspace ships with a default GTD state sequence and an extended nightshift sequence for autonomous execution: + +```python +from org_workspace import StateConfig + +# Default GTD: TODO → NEXT → WAITING → DONE +config = StateConfig.default() + +# Nightshift: adds QUEUED, EXECUTING, REVIEW, FAILED for agent pipelines +config = StateConfig.nightshift() +ws = OrgWorkspace(roots=[...], state_config=config) +``` + +## Where to go next + +- [GitHub](https://github.com/datacore-one/org-workspace) — source, issues, discussions +- `pip install org-workspace` then `python -c "from org_workspace import OrgWorkspace; help(OrgWorkspace)"` +- Issues and feature requests welcome on GitHub