Knots is a local-first, git-backed system for helping humans and agents understand what they did, what they are doing, and what they plan to do next. It uses append-only events plus a SQLite cache to stay fast locally while remaining syncable through git.
The installer pulls from GitHub Releases and installs to ${HOME}/.local/bin by default.
curl -fsSL https://raw.githubusercontent.com/acartine/knots/main/install.sh | shKnots is intentionally a lighter, thinner descendant of Beads.
Beads opened up a powerful way to think about structured memory, workflow, and human/agent collaboration. Knots keeps that lineage, but aims for a simpler local-first shape: fast CLI workflows, repo-native data, opinionated queue/action transitions, and less dependence on a larger orchestration platform.
A knot is not just a unit of work. It is a unit of coordination and understanding. A knot can represent work, but it can also represent a gate, and future versions may grow other kinds such as agents or teams. The point is to keep a durable record of what matters to humans and agents as they move through a process.
Each step of the workflow is either an action state or a queue state.
- Action states mean something is actively being worked.
- Queue states mean something is ready to be picked up by the next responsible actor.
That split keeps it obvious what is in progress, what is waiting, and what should happen next.
Some workflows also define passive escape states such as blocked or
deferred. Those states are non-terminal waiting states: they are not
claimable work, and they do not imply that the knot is done.
Knots provides one core workflow with multiple profiles. A profile assigns ownership to actions and, in some cases, defines what a step is expected to produce.
For example, an Implementation Review step can be human-gated, and its review target might be a branch, a PR, or a merged commit. That gives you fine-grained control over what agents are allowed to do and what counts as done.
Different knots can use different profiles. A small patch might skip planning and review, while a larger feature can go through the full workflow.
graph TD
classDef queue fill:#F2F3F4,stroke:#AAB7B8,color:#555555,stroke-width:1px
classDef action fill:#D6EAF8,stroke:#2E86C1,color:#1A5276,font-weight:bold,stroke-width:3px
classDef terminal fill:#E8F8F5,stroke:#17A589,color:#0E6251,font-weight:bold,stroke-width:2px
START(( )) --> QP(Ready for Planning):::queue
%% --- Planning ---
QP -->|start| P([Planning]):::action
P -->|finish| QPR(Ready for Plan Review):::queue
QPR -->|start| PR([Plan Review]):::action
PR -->|approve| QI(Ready for Implementation):::queue
PR -->|request changes| QP:::queue
%% --- Implementation ---
QI -->|start| I([Implementation]):::action
I -->|finish| QIR(Ready for Implementation Review):::queue
QIR -->|start| IR([Implementation Review]):::action
IR -->|approve| QS(Ready for Shipment):::queue
IR -->|request changes| QI:::queue
%% --- Shipment ---
QS -->|start| S([Shipment]):::action
S -->|finish| QSR(Ready for Shipment Review):::queue
QSR -->|start| SR([Shipment Review]):::action
%% --- Shipment outcomes / routing ---
SR -->|approved| SHIPPED[[Shipped]]:::terminal
SR -->|failed| RCA{Failure caused by implementation?}:::action
RCA -->|yes| QI:::queue
RCA -->|no| QS:::queue
SHIPPED --> END(( ))
If you only want the shortest path to seeing Knots work, it is this:
kno initkno new "fix foo" --desc "The foo module panics on empty input"kno poll --claim- do the work described in the prompt
- run the
kno next ...command printed in the prompt
The fuller walkthrough is below.
$ kno init═══════════════════════════════════════════
FIT TO BE TIED 🎉
═══════════════════════════════════════════
▸ initializing local store
▸ opening cache database at .knots/cache/state.sqlite
▸ ensuring gitignore includes .knots rule
✔ local store initialized
▸ initializing remote branch origin/knots
⋯ this can take a bit...
✔ remote branch origin/knots initialized
This creates the .knots/ directory, initializes the SQLite cache, adds .knots/ to .gitignore, and sets up the origin/knots tracking branch.
kno init is also how you onboard to a repo that already uses Knots. If a project's README says it uses Knots, just run kno init in your clone. Instead of creating a new remote tracking branch, it will detect the existing origin/knots branch and sync you with the latest Knots data.
$ kno new "fix foo" --desc "The foo module panics on empty input"created abc123 ready_for_planning fix foo
The knot enters the first queue state (ready_for_planning) and is immediately available for the next responsible actor to pick up — often an agent, but not necessarily.
$ kno poll --claim# fix foo
**ID**: abc123 | **Priority**: 3 | **Type**: work
**Profile**: autopilot | **State**: planning
## Description
The foo module panics on empty input
---
# Planning
## Input
- Knot in `ready_for_planning` state
- Knot title, description, and any existing notes/context
## Actions
1. Analyze the knot requirements and constraints
2. Research relevant code, dependencies, and prior art
3. Draft an implementation plan with steps, file changes, and test strategy
4. Estimate complexity and identify risks
5. Write the plan as a knot note via `kno update <id> --add-note "<plan>"`
6. Create a hierarchy of knots via `kno new "<title>"` for parent knots, `kno q "title"` for child knots and `kno edge <id> parent_of <id>` for edges
## Output
- Detailed implementation plan attached as a knot note
- Hierarchy of knots created
- Transition:
```bash
kno next <id> --expected-state <currentState> --actor-kind agent \
--agent-name <AGENT_NAME> --agent-model <AGENT_MODEL> \
--agent-version <AGENT_VERSION>
- Insufficient context:
kno update <id> --status ready_for_planning --add-note "<note>" - Out of scope / too complex:
kno update <id> --status ready_for_planning --add-note "<note>"
`poll --claim` atomically grabs the highest-priority claimable item, transitions it from a queue state to its action state, and prints a self-contained prompt. The output includes the knot context, the instructions for the current step, and the exact command to run when that step is done.
### 4. Advance to the next state
When the current actor finishes the step, run the completion command from the prompt:
```bash
$ kno next abc123 --expected-state planning --actor-kind agent \
--agent-name my-agent --agent-model my-model \
--agent-version 1.0.0
updated abc123 -> ready_for_plan_review
The knot moves to the next queue state, where it waits for the next action to be claimed.
For an automated worker, the loop is just two commands:
while true; do
kno poll --claim || { sleep 30; continue; }
# ... do the work described in the prompt ...
# ... run the completion command from the output ...
doneEach iteration claims work, executes it, and advances the knot through the workflow until it reaches shipped.
poll and claim are the primary agent interface. CLI stdout is the prompt delivery mechanism — no file injection, no hooks, and no agent-specific API required.
kno poll # peek at the top claimable knot
kno poll implementation # filter to a specific stage
kno poll --owner human # show human-owned stages instead
kno poll --claim # atomically grab the top item
kno poll --claim --json # machine-readable output
kno claim <id> # claim a specific knot by id
kno claim <id> --json # machine-readable claim
kno claim <id> --peek # preview without advancing stateAgent metadata is recorded on each claim:
kno claim <id> \
--agent-name "claude-code" \
--agent-model "opus-4" \
--agent-version "1.0"Downstream tools can read stable routing metadata from both live knot views and persisted knot-head events.
kno show <id> --jsonreturnsstep_metadatafor the current state andnext_step_metadatafor the next happy-path state.kno ls --jsonincludes the same fields on each listed knot..knots/index/.../idx.knot_head.jsonpersists the same metadata in event logs for replay or external ingestion.
Each metadata object has a stable shape:
{
"action_state": "implementation_review",
"action_kind": "review",
"owner": { "kind": "human" },
"output": {
"artifact_type": "approval",
"access_hint": "git log"
},
"review_hint": "Check tests pass and coverage meets threshold"
}Use owner.kind to route the current or next action to a human or agent,
output.artifact_type and output.access_hint to decide what artifact a step
should produce, and review_hint to tell reviewers what to inspect.
A lease is a session token automatically created when an agent claims a knot and
terminated when the agent advances (kno next). Every claim gets its own
dedicated lease — they are never shared. Leases block sync (push/pull) while
active, preventing in-progress work from replicating to other machines.
Leases expire after a configurable timeout (default: 10 minutes). Write commands that touch a bound knot automatically refresh the timer. Expired leases are lazily terminated on the next interaction and unblock sync.
For full lifecycle details, timeout configuration, extension, and manual management commands see docs/leases.md.
Both commands support --json for programmatic consumption:
{
"id": "K-abc123",
"title": "fix foo",
"state": "planning",
"priority": 3,
"type": "work",
"profile_id": "autopilot",
"prompt": "# fix foo\n\n**ID**: abc123 ..."
}Any agent runtime (the command output IS the prompt):
kno poll --claim | agent-runner --prompt -Programmatic (Python, SDK, etc.):
result = subprocess.run(["kno", "poll", "--claim", "--json"],
capture_output=True)
item = json.loads(result.stdout)
agent.run(prompt=item["prompt"])Knots can install its managed knots and knots-e2e skills for supported
agent tools:
kno skills install codex
kno skills install claude
kno skills install opencodeClaude support is project-level only at ./.claude/skills. OpenCode installs
to the project root when ./.opencode/ exists; otherwise it falls back to the
supported user-level root. kno doctor checks whether those expected skills
are present and reports missing setup with the exact SKILL.md paths.
CI/CD:
- run: |
WORK=$(kno poll --json)
if [ -n "$WORK" ]; then
kno claim $(echo $WORK | jq -r .id) --json | agent-runner
fikno --versionkno upgrade
kno upgrade --version v0.2.0kno uninstall
kno uninstall --remove-previouskno new "Document release pipeline" --state ready_for_implementation
kno new "Triage regression" # uses repo default profile
kno new "Hotfix gate" --profile semiautokno state <knot-id> implementationkno next <knot-id> implementation
kno rollback <knot-id>
kno rb <knot-id> --dry-runrollback moves action states back to the prior ready state; for example,
implementation_review rewinds to ready_for_implementation.
kno update <knot-id> \
--title "Refine import reducer" \
--description "Carry full migration metadata" \
--priority 1 \
--status implementation \
--type work \
--add-tag migration \
--add-note "handoff context" \
--note-username acartine \
--note-datetime 2026-02-23T10:00:00Z \
--note-agentname codex \
--note-model gpt-5 \
--note-version 0.1kno ls
kno ls # shipped knots hidden by default
kno ls --all # include shipped knots
kno ls --state implementation --tag release
kno ls --profile semiauto
kno ls --type work --query importer
kno show <knot-id>
kno show <knot-id> --jsonkno synckno edge add <src-id> blocked_by <dst-id>
kno edge list <src-id> --direction outgoing
kno edge remove <src-id> blocked_by <dst-id>Import supports parity fields when present:
description,priority,issue_type/typelabels/tagsnotesas legacy string or structured array entrieshandoff_capsulesstructured array entries
Knots uses SQLite in WAL mode with a busy timeout, and concurrency must follow these rules:
- Queue and serialize write operations so only one writer mutates the cache at a time.
- Allow read operations to execute immediately; reads must not be queued behind writes.
- Keep
PRAGMA journal_mode=WALenabled to preserve snapshot reads during writes. - Keep
PRAGMA busy_timeoutconfigured and treatSQLITE_BUSY/SQLITE_LOCKEDas retryable. - Add bounded write retries with jittered backoff.
- Keep write transactions short; avoid long-lived write locks.
- For commands requiring strict read-after-write freshness, run the read after the queued write commits.
Implementation note:
- Current cache setup is in
src/db.rs(journal_mode=WAL,busy_timeout=5000).
For information on the release process and local development testing, see CONTRIBUTING.md.
- Security policy: see
SECURITY.md - Non-security bugs/feature work: open a normal GitHub issue
- Installation/release regressions: open issue with logs and platform details
After publishing the repository:
- Open repository
Settings. - Open
Security & analysis. - Enable
Private vulnerability reporting. - Confirm
SECURITY.mdis discoverable from the repository root.
MIT. See LICENSE.