agentgraph-server 0.5.2__tar.gz → 0.5.3__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.
Files changed (98) hide show
  1. agentgraph_server-0.5.3/.agents/skills/AgentGraph/SKILL.md +76 -0
  2. agentgraph_server-0.5.3/.agents/skills/AgentGraph/references/commands.md +45 -0
  3. agentgraph_server-0.5.3/.agents/skills/AgentGraph/references/data-model.md +51 -0
  4. agentgraph_server-0.5.3/.agents/skills/AgentGraph/references/operations.md +77 -0
  5. {agentgraph_server-0.5.2/agentgraph_server.egg-info → agentgraph_server-0.5.3}/PKG-INFO +6 -6
  6. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/README.md +5 -5
  7. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/backends/sqlite/backend.py +80 -37
  8. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/backends/sqlite/schema.sql +1 -1
  9. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/cli.py +25 -14
  10. agentgraph_server-0.5.3/agentgraph/cli_query.py +355 -0
  11. agentgraph_server-0.5.3/agentgraph/cli_sync.py +106 -0
  12. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/connectors/base.py +10 -1
  13. agentgraph_server-0.5.3/agentgraph/connectors/command_effects.py +18 -0
  14. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/core/runtime.py +5 -1
  15. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/demo.py +4 -30
  16. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/delete.py +9 -0
  17. agentgraph_server-0.5.3/agentgraph/graph/operations.py +98 -0
  18. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/mcp/server.py +255 -88
  19. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/app.py +31 -37
  20. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/cli_api.py +37 -254
  21. agentgraph_server-0.5.3/agentgraph/server/meta_api.py +51 -0
  22. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/static/viewer.html +1 -1
  23. agentgraph_server-0.5.3/agentgraph/server/sync_api.py +67 -0
  24. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/skills.py +38 -4
  25. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3/agentgraph_server.egg-info}/PKG-INFO +6 -6
  26. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph_server.egg-info/SOURCES.txt +11 -2
  27. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/pyproject.toml +5 -2
  28. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_benchmarks.py +6 -4
  29. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_browse.py +40 -24
  30. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_cli.py +284 -94
  31. agentgraph_server-0.5.3/tests/test_command_effects.py +59 -0
  32. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_docs_build.py +79 -14
  33. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_expiration.py +18 -0
  34. agentgraph_server-0.5.3/tests/test_graph_operations.py +215 -0
  35. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_launch_demo.py +13 -9
  36. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_observation.py +16 -10
  37. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_query.py +166 -91
  38. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_release_metadata.py +1 -1
  39. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_rss_connector.py +39 -1
  40. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_schema.py +61 -0
  41. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_slack_auth_artifacts.py +8 -27
  42. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_sync.py +19 -0
  43. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_viewer_layout_contract.py +3 -1
  44. agentgraph_server-0.5.2/.agents/skills/graph/SKILL.md +0 -163
  45. agentgraph_server-0.5.2/agentgraph/cli_query.py +0 -514
  46. agentgraph_server-0.5.2/agentgraph/server/graph_api.py +0 -46
  47. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/.agents/skills/slack-auth/SKILL.md +0 -0
  48. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/LICENSE +0 -0
  49. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/__init__.py +0 -0
  50. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/auth/__init__.py +0 -0
  51. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/auth/credentials.py +0 -0
  52. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/backends/__init__.py +0 -0
  53. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/backends/sqlite/__init__.py +0 -0
  54. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/backends/sqlite/vector.py +0 -0
  55. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/config.py +0 -0
  56. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/connectors/__init__.py +0 -0
  57. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/connectors/registry.py +0 -0
  58. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/connectors/status.py +0 -0
  59. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/core/__init__.py +0 -0
  60. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/core/context.py +0 -0
  61. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/core/storage.py +0 -0
  62. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
  63. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
  64. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/__init__.py +0 -0
  65. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/bookmark.py +0 -0
  66. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/download.py +0 -0
  67. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/embeddings.py +0 -0
  68. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/expiration.py +0 -0
  69. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/fetch.py +0 -0
  70. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/link.py +0 -0
  71. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/person.py +0 -0
  72. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/query.py +0 -0
  73. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/graph/upsert.py +0 -0
  74. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/logging.py +0 -0
  75. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/mcp/__init__.py +0 -0
  76. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/perf.py +0 -0
  77. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/__init__.py +0 -0
  78. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/observation.py +0 -0
  79. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/router.py +0 -0
  80. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph/server/sync.py +0 -0
  81. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph_server.egg-info/dependency_links.txt +0 -0
  82. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph_server.egg-info/entry_points.txt +0 -0
  83. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph_server.egg-info/requires.txt +0 -0
  84. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/agentgraph_server.egg-info/top_level.txt +0 -0
  85. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/setup.cfg +0 -0
  86. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_auth.py +0 -0
  87. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_config.py +0 -0
  88. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_connectors.py +0 -0
  89. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_discord_attachments.py +0 -0
  90. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_embeddings.py +0 -0
  91. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_fetch.py +0 -0
  92. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_logging.py +0 -0
  93. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_registry.py +0 -0
  94. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_router.py +0 -0
  95. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_server_imports.py +0 -0
  96. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_slack_oauth.py +0 -0
  97. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_upsert.py +0 -0
  98. {agentgraph_server-0.5.2 → agentgraph_server-0.5.3}/tests/test_web_connector.py +0 -0
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: AgentGraph
3
+ description: Use AgentGraph through its CLI or MCP tools to search and traverse selected local context, retrieve full source-backed evidence, fetch missing or stale resources, and troubleshoot connector availability.
4
+ ---
5
+
6
+ # AgentGraph CLI skill
7
+
8
+ AgentGraph is a local knowledge graph for the agent the user already uses. It stores
9
+ selected messages, documents, people, feeds, pages, and relationships; the agent is
10
+ responsible for searching that graph, comparing evidence, and explaining its reasoning.
11
+
12
+ Prefer the `agentgraph` CLI when a shell is available. Use the equivalent MCP tools
13
+ when AgentGraph is connected directly to the agent. Do not query AgentGraph's SQLite
14
+ database or connector internals directly.
15
+
16
+ ## Commands that use localhost
17
+
18
+ `agentgraph poll` and connector or authentication commands that queue a poll or
19
+ historical ingest call the configured local AgentGraph HTTP server. If a sandbox blocks
20
+ one of these commands, request permission to contact that localhost server and retry
21
+ the command after approval.
22
+
23
+ ## Investigation workflow
24
+
25
+ 1. Discover likely entities with `agentgraph search "<query>" --json` or
26
+ `search_entities_tool`.
27
+ 2. Open promising results with `agentgraph get <target> --json` or
28
+ `get_entity_tool` to read full content and source metadata. Search and query results
29
+ contain bounded snippets and set `content_truncated` when content was shortened.
30
+ 3. Follow relationships with `agentgraph edges`, `agentgraph traverse`,
31
+ `get_edges_tool`, or `traverse_graph_tool`.
32
+ 4. If an entity is a stub, or known context is stale, resolve or re-fetch it through
33
+ the owning connector and then repeat the read or traversal.
34
+ 5. Compare source dates and contents. Distinguish source facts from inference and cite
35
+ each source URL or entity identifier used.
36
+
37
+ Start with search unless the user already supplied a graph ID, platform reference, or
38
+ known indexed URL. Use structured `query` only when the entity type or filters are
39
+ already known.
40
+
41
+ ## Context lifecycle
42
+
43
+ - **Connect** is setup: installed connectors and their authentication/configuration
44
+ determine which selected sources AgentGraph can access.
45
+ - **Observe** records human attention to a supported browser URL and triggers a
46
+ targeted connector fetch.
47
+ - **Fetch** retrieves missing or stale context at the agent's request.
48
+ - **Refresh** uses polling or connector-owned ingest commands to update configured or
49
+ already-known context.
50
+
51
+ Only browser observation updates `observed_at`. Direct fetch, polling, and ingest can
52
+ change graph content without implying that the human viewed it.
53
+
54
+ ## Evidence rules
55
+
56
+ - Use full entity content before making a source-backed claim; do not treat a search
57
+ snippet as the complete source.
58
+ - Treat an entity with no title and no content as an unresolved stub.
59
+ - Use source timestamps for chronology. `created_at` and `updated_at` describe the
60
+ local graph record; `source_created_at` and `source_updated_at` describe the source.
61
+ - Chat uploads live on `Message.metadata.attachments`. Gmail attachments are
62
+ `Document` stubs referenced by their owning `Email` and are downloaded separately.
63
+ - Inspect connector and auth state only when freshness matters, a fetch is required,
64
+ or a graph operation reports a connector or credential problem.
65
+ - Never merge Person entities without user confirmation. Treat delete, credential
66
+ removal, and unbookmarking as destructive actions.
67
+
68
+ ## References
69
+
70
+ - Read [references/commands.md](references/commands.md) for CLI syntax, MCP mappings,
71
+ target formats, stub resolution, and result-size behavior.
72
+ - Read [references/data-model.md](references/data-model.md) for entity types, edges,
73
+ attachment representation, timestamps, and retention semantics.
74
+ - Read [references/operations.md](references/operations.md) for connectors, auth,
75
+ polling, connector-owned commands, downloads, bookmarks, deletion, person merging,
76
+ server setup, and skill installation.
@@ -0,0 +1,45 @@
1
+ # Query and traversal commands
2
+
3
+ ## CLI
4
+
5
+ ```bash
6
+ agentgraph search "<query>" [--type <type>] [--platform <platform>] [--limit N] [--min-score N] [--json]
7
+ agentgraph query --type <entity-type> [--filter key=value] [--since 12h|30m|2d] [--mine] [--has-attachments] [--limit N] [--order-by created_at|updated_at|source_created_at|source_updated_at|observed_at|synced_at] [--json]
8
+ agentgraph get <entity-id|platform/ref|url> [--resolve] [--json]
9
+ agentgraph edges <entity-id|platform/ref> [--type <edge-type>] [--direction in|out|both] [--json]
10
+ agentgraph traverse <entity-id|platform/ref> [--resolve] [--depth 0..4] [--json]
11
+ ```
12
+
13
+ `get` and `traverse` do not resolve stubs unless `--resolve` is supplied. Depth 0
14
+ returns only the starting entity; depths 1 through 4 include that many relationship
15
+ hops.
16
+
17
+ Use `--json` when results will be parsed programmatically. Search and query return
18
+ bounded content snippets with `content_truncated`; use `get` for the complete entity.
19
+
20
+ Targets accepted by `get` are full UUIDs, unambiguous UUID prefixes, platform
21
+ references such as `slack/T123/C123`, and indexed HTTP(S) URLs. `edges` and `traverse`
22
+ accept full UUIDs, unambiguous UUID prefixes, and platform references.
23
+
24
+ ## MCP equivalents
25
+
26
+ ```text
27
+ agentgraph search ... -> search_entities_tool(query, entity_types, platform, limit, min_score, refresh)
28
+ agentgraph query ... -> query_by_filter_tool(entity_type, filters, since, authored_by_me, has_attachments, limit, order_by, refresh)
29
+ agentgraph get ... -> get_entity_tool(entity_id, resolve)
30
+ agentgraph edges ... -> get_edges_tool(entity_id, edge_type, direction)
31
+ agentgraph traverse ... -> traverse_graph_tool(entity_id, max_depth, resolve)
32
+ ```
33
+
34
+ MCP search and query default to `refresh=false`; set it only when fresh
35
+ connector-owned presentation metadata is needed. It does not replace a targeted
36
+ source fetch for stale content.
37
+
38
+ ## Stubs
39
+
40
+ An entity is a stub when it has neither a title nor content. With the CLI, pass
41
+ `--resolve` to `get` or `traverse`. With MCP, set `resolve=true`, or call
42
+ `fetch_entity_by_id_tool` for the stub UUID and repeat the original operation.
43
+
44
+ For a known platform resource that is absent from the graph, use `agentgraph fetch
45
+ <platform> <resource-id>` or `fetch_entity_tool(platform, resource_id)`.
@@ -0,0 +1,51 @@
1
+ # AgentGraph data model
2
+
3
+ ## Entity types
4
+
5
+ | Type | Contains |
6
+ |---|---|
7
+ | `Channel` | Chat channels and DM threads. |
8
+ | `Document` | Text documents, feed/web pages, Drive files, and Gmail attachment stubs. |
9
+ | `Email` | Gmail email threads. |
10
+ | `Folder` | Drive folders and RSS feed containers. |
11
+ | `Message` | Chat messages. Chat images and uploads are stored in `metadata.attachments`. |
12
+ | `Person` | Source identities and confirmed cross-source identity merges. |
13
+ | `Spreadsheet` | Google Sheets and other spreadsheet resources. |
14
+
15
+ The valid core model does not currently include `Task` or `Project`.
16
+
17
+ ## Relationships
18
+
19
+ Edges connect entities with types such as `authored`, `participated_in`, `posted_in`,
20
+ `replied_to`, `mentions`, `contains`, and `references`. Use direct edges when one-hop
21
+ context is sufficient and traversal when the investigation needs a bounded subgraph.
22
+
23
+ ## Attachments
24
+
25
+ Chat photos and file uploads are attachments on `Message` entities. Query them with:
26
+
27
+ ```bash
28
+ agentgraph query --type Message --has-attachments --since 7d --json
29
+ ```
30
+
31
+ `metadata.attachments` is a JSON array containing `url`, `filename`, `content_type`,
32
+ and optional `width` and `height` fields.
33
+
34
+ Gmail attachments are different: they are `Document` stubs referenced by the owning
35
+ `Email`. Re-fetch the email thread, traverse one hop, then download the attachment
36
+ document.
37
+
38
+ ## Timestamps and retention
39
+
40
+ - `created_at`: local graph insertion time.
41
+ - `updated_at`: last material change to the stored entity.
42
+ - `source_created_at`: source-reported creation or publication time.
43
+ - `source_updated_at`: source-reported modification time.
44
+ - `synced_at`: last successful connector synchronization.
45
+ - `observed_at`: last accepted browser observation of that exact observable entity.
46
+
47
+ Observable entities expire from `observed_at`, or local `created_at` if never
48
+ observed. Messages and Gmail attachment documents follow their parent. Persons remain
49
+ while connected. Configured RSS feed Folders are persistent source entities and are
50
+ removed by the RSS connector's `remove` command or explicit deletion. Bookmarks protect
51
+ an entity from automatic expiration.
@@ -0,0 +1,77 @@
1
+ # AgentGraph operations
2
+
3
+ ## Availability and authentication
4
+
5
+ ```bash
6
+ agentgraph connectors [--verify] [--json]
7
+ agentgraph auth [--verify] [--json] status
8
+ agentgraph auth <provider> [--add] [--account <account-id>]
9
+ agentgraph auth remove <provider> [--account <account-id>] [--json]
10
+ ```
11
+
12
+ Use `agentgraph connectors --json` to discover installed connector source names,
13
+ valid platform values, URL ownership, polling delegation, and sync state. Do not rely
14
+ on a hardcoded platform list. Use `--verify` only when a live provider check is
15
+ needed.
16
+
17
+ MCP equivalents are `list_connectors_tool`, `list_auth_providers_tool`,
18
+ `authenticate_provider_tool`, and `remove_auth_provider_tool`.
19
+
20
+ ## Fetch and refresh
21
+
22
+ ```bash
23
+ agentgraph fetch <platform> <resource-id> [--json]
24
+ agentgraph fetch-entity <entity-id> [--json]
25
+ agentgraph poll [<source>] [--json]
26
+ agentgraph connector <source> <command> [args...] [--json]
27
+ agentgraph connector <source> --help
28
+ ```
29
+
30
+ The MCP equivalents are `fetch_entity_tool`, `fetch_entity_by_id_tool`,
31
+ `poll_connectors_tool`, and `run_connector_command_tool`.
32
+
33
+ Fetch persists the connector's complete returned batch before reporting counts and
34
+ does not update `observed_at`. Poll reports `polled`, `already_running`, and `skipped`.
35
+ A connector can be refreshed by another connector, so also inspect `polled_by` and
36
+ `sync` in connector status.
37
+
38
+ Historical ingest is connector-owned. Discover it through connector help. For
39
+ example, Gmail exposes:
40
+
41
+ ```bash
42
+ agentgraph connector gmail ingest [--account <account-id>] [--json]
43
+ ```
44
+
45
+ The MCP form is `run_connector_command_tool("gmail", ["ingest", ...])`. There is no
46
+ top-level `agentgraph ingest` command or `ingest_connector_tool`.
47
+
48
+ ## Files and retained context
49
+
50
+ ```bash
51
+ agentgraph download <entity-id|platform/ref> [--output <file-or-dir>] [--json]
52
+ agentgraph bookmark <entity-id|platform/ref|url> [--remove] [--json]
53
+ agentgraph delete <entity-id|platform/ref|url> [--json]
54
+ agentgraph unify-persons <primary-person-id> <duplicate-person-id>... [--json]
55
+ ```
56
+
57
+ Use `download_entity_tool`, `bookmark_entity_tool`, `delete_entity_tool`, and
58
+ `unify_persons_tool` through MCP. Confirm identity before person unification. The
59
+ first Person is canonical and keeps its ID. Deletion removes connected edges.
60
+
61
+ ## Server and skill setup
62
+
63
+ `agentgraph serve` runs the required local AgentGraph service. Follow the environment's
64
+ process-management instructions rather than starting a duplicate foreground server
65
+ when a service manager already owns it.
66
+
67
+ `agentgraph poll` and connector or authentication commands that queue a poll or ingest
68
+ contact that service over localhost. If sandboxed execution blocks one of those
69
+ commands, request permission to contact the configured localhost server and retry it.
70
+
71
+ ```bash
72
+ agentgraph mcp-config
73
+ agentgraph mcp-serve
74
+ agentgraph install-skill [AgentGraph] [--target user|project] [--no-claude] [--force] [--json]
75
+ ```
76
+
77
+ The skill installer is available only through the `agentgraph install-skill` CLI command.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentgraph-server
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: A local-first CLI and MCP server that turns selected digital sources into a searchable graph for the AI agent you already use.
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -46,11 +46,11 @@ AgentGraph is a local-first, self-hosted CLI and MCP server that turns the sourc
46
46
 
47
47
  > **AgentGraph stores context; your agent reasons over it.** It is not an agent, chatbot, hosted graph service, or replacement for the tools you already use.
48
48
 
49
- [See the demo](docs-src/demo.md) · [Quickstart](docs-src/quickstart.md) · [Documentation](https://simonexmachina.github.io/agent-graph/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
49
+ [See the demo](docs-src/demo.md) · [Install](docs-src/install.md) · [Documentation](https://simonexmachina.github.io/agent-graph/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
50
50
 
51
51
  <picture>
52
52
  <source media="(prefers-color-scheme: dark)" srcset="docs-src/assets/diagrams/architecture-overview-dark.svg">
53
- <img src="docs-src/assets/diagrams/architecture-overview-light.svg" alt="Selected online services connect to AgentGraph on the user's machine. Browser observation, agent fetches, and background refresh flow through connector packages into a local graph, which is exposed to the user's existing coding agent through MCP.">
53
+ <img src="docs-src/assets/diagrams/architecture-overview-light.svg" alt="Observe, Fetch, and Refresh converge on connector packages that read selected services and write to a local graph. Agents access the graph through the CLI or MCP, while Expiry applies the retention model.">
54
54
  </picture>
55
55
 
56
56
  ## Why AgentGraph
@@ -121,7 +121,7 @@ Connect the agent you already use:
121
121
  agentgraph mcp-config
122
122
  ```
123
123
 
124
- Use the printed stdio configuration with Claude Desktop, Claude Code, Codex, or another compatible MCP client. ChatGPT developer mode uses the streamable HTTP `/mcp` endpoint through HTTPS. The [Quickstart](docs-src/quickstart.md) covers the complete path and expected result.
124
+ Use the printed stdio configuration with Claude Desktop, Claude Code, Codex, or another compatible MCP client. ChatGPT developer mode uses the streamable HTTP `/mcp` endpoint through HTTPS. See [Install](docs-src/install.md) for the complete setup path.
125
125
 
126
126
  ## What AgentGraph is and is not
127
127
 
@@ -152,7 +152,7 @@ An MCP client you connect can receive content from the local graph, subject to t
152
152
  ## Documentation
153
153
 
154
154
  - [Install](docs-src/install.md)
155
- - [Quickstart](docs-src/quickstart.md)
155
+ - [Install](docs-src/install.md)
156
156
  - [How AgentGraph works](docs-src/how-it-works.md)
157
157
  - [Connectors](docs-src/connectors.md)
158
158
  - [Configuration](docs-src/configuration.md)
@@ -165,7 +165,7 @@ An MCP client you connect can receive content from the local graph, subject to t
165
165
  See [AGENTS.md](AGENTS.md) for repository-specific development rules. Before opening a change, run:
166
166
 
167
167
  ```bash
168
- uv run pytest tests/ -m "not integration" -q
168
+ uv run pytest tests/ -m "not integration and not browser" -q
169
169
  uv run pyright
170
170
  uv run ruff check agentgraph/ packages/ scripts/ tests/
171
171
  ```
@@ -6,11 +6,11 @@ AgentGraph is a local-first, self-hosted CLI and MCP server that turns the sourc
6
6
 
7
7
  > **AgentGraph stores context; your agent reasons over it.** It is not an agent, chatbot, hosted graph service, or replacement for the tools you already use.
8
8
 
9
- [See the demo](docs-src/demo.md) · [Quickstart](docs-src/quickstart.md) · [Documentation](https://simonexmachina.github.io/agent-graph/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
9
+ [See the demo](docs-src/demo.md) · [Install](docs-src/install.md) · [Documentation](https://simonexmachina.github.io/agent-graph/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
10
10
 
11
11
  <picture>
12
12
  <source media="(prefers-color-scheme: dark)" srcset="docs-src/assets/diagrams/architecture-overview-dark.svg">
13
- <img src="docs-src/assets/diagrams/architecture-overview-light.svg" alt="Selected online services connect to AgentGraph on the user's machine. Browser observation, agent fetches, and background refresh flow through connector packages into a local graph, which is exposed to the user's existing coding agent through MCP.">
13
+ <img src="docs-src/assets/diagrams/architecture-overview-light.svg" alt="Observe, Fetch, and Refresh converge on connector packages that read selected services and write to a local graph. Agents access the graph through the CLI or MCP, while Expiry applies the retention model.">
14
14
  </picture>
15
15
 
16
16
  ## Why AgentGraph
@@ -81,7 +81,7 @@ Connect the agent you already use:
81
81
  agentgraph mcp-config
82
82
  ```
83
83
 
84
- Use the printed stdio configuration with Claude Desktop, Claude Code, Codex, or another compatible MCP client. ChatGPT developer mode uses the streamable HTTP `/mcp` endpoint through HTTPS. The [Quickstart](docs-src/quickstart.md) covers the complete path and expected result.
84
+ Use the printed stdio configuration with Claude Desktop, Claude Code, Codex, or another compatible MCP client. ChatGPT developer mode uses the streamable HTTP `/mcp` endpoint through HTTPS. See [Install](docs-src/install.md) for the complete setup path.
85
85
 
86
86
  ## What AgentGraph is and is not
87
87
 
@@ -112,7 +112,7 @@ An MCP client you connect can receive content from the local graph, subject to t
112
112
  ## Documentation
113
113
 
114
114
  - [Install](docs-src/install.md)
115
- - [Quickstart](docs-src/quickstart.md)
115
+ - [Install](docs-src/install.md)
116
116
  - [How AgentGraph works](docs-src/how-it-works.md)
117
117
  - [Connectors](docs-src/connectors.md)
118
118
  - [Configuration](docs-src/configuration.md)
@@ -125,7 +125,7 @@ An MCP client you connect can receive content from the local graph, subject to t
125
125
  See [AGENTS.md](AGENTS.md) for repository-specific development rules. Before opening a change, run:
126
126
 
127
127
  ```bash
128
- uv run pytest tests/ -m "not integration" -q
128
+ uv run pytest tests/ -m "not integration and not browser" -q
129
129
  uv run pyright
130
130
  uv run ruff check agentgraph/ packages/ scripts/ tests/
131
131
  ```
@@ -63,6 +63,8 @@ _LIST_PAGE_ORDER_BY = {
63
63
  }
64
64
  _COLUMN_FILTERS = {"platform", "platform_entity_id", "entity_type"}
65
65
  _FTS_DELETE_CHUNK_SIZE = 500
66
+ _BUSY_TIMEOUT_MS = 5_000
67
+ _SCHEMA_VERSION = 2
66
68
 
67
69
 
68
70
  def _now() -> str:
@@ -126,36 +128,33 @@ class SQLiteBackend(StorageBackend):
126
128
 
127
129
  self._conn = await aiosqlite.connect(self._db_path, isolation_level=None)
128
130
  self._conn.row_factory = sqlite3.Row
129
- if self._db_path == ":memory:":
130
- self._read_conn = self._conn
131
-
132
- if self._db_path != ":memory:":
133
- await self._conn.execute("PRAGMA journal_mode=WAL")
134
- await self._conn.execute("PRAGMA foreign_keys=ON")
135
- legacy_table = await self._conn.execute(
136
- "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'entities'"
137
- )
138
- if await legacy_table.fetchone():
139
- cursor = await self._conn.execute("PRAGMA table_info(entities)")
140
- existing_columns = {row["name"] for row in await cursor.fetchall()}
141
- if "observed_at" not in existing_columns:
142
- await self._conn.execute("ALTER TABLE entities ADD COLUMN observed_at TEXT")
143
- await self._conn.executescript(_SCHEMA_SQL)
144
-
145
- await self._run_migrations()
146
-
147
- if self._db_path != ":memory:":
148
- self._read_conn = await aiosqlite.connect(self._db_path, isolation_level=None)
149
- self._read_conn.row_factory = sqlite3.Row
150
- await self._read_conn.execute("PRAGMA foreign_keys=ON")
151
-
152
- if self._vector_mode == "sqlite-vec":
153
- assert self._read_conn is not None
154
- self._vec_loaded = await load_sqlite_vec(self._read_conn)
155
- if self._vec_loaded:
156
- logger.info("sqlite-vec extension loaded")
131
+ try:
132
+ await self._conn.execute(f"PRAGMA busy_timeout={_BUSY_TIMEOUT_MS}")
133
+ if self._db_path == ":memory:":
134
+ self._read_conn = self._conn
157
135
  else:
158
- logger.info("sqlite-vec not available, falling back to numpy")
136
+ await self._ensure_wal_mode()
137
+ await self._conn.execute("PRAGMA foreign_keys=ON")
138
+
139
+ if await self._schema_version() < _SCHEMA_VERSION:
140
+ await self._initialize_schema()
141
+
142
+ if self._db_path != ":memory:":
143
+ self._read_conn = await aiosqlite.connect(self._db_path, isolation_level=None)
144
+ self._read_conn.row_factory = sqlite3.Row
145
+ await self._read_conn.execute(f"PRAGMA busy_timeout={_BUSY_TIMEOUT_MS}")
146
+ await self._read_conn.execute("PRAGMA foreign_keys=ON")
147
+
148
+ if self._vector_mode == "sqlite-vec":
149
+ assert self._read_conn is not None
150
+ self._vec_loaded = await load_sqlite_vec(self._read_conn)
151
+ if self._vec_loaded:
152
+ logger.info("sqlite-vec extension loaded")
153
+ else:
154
+ logger.info("sqlite-vec not available, falling back to numpy")
155
+ except Exception:
156
+ await self.close()
157
+ raise
159
158
 
160
159
  async def close(self) -> None:
161
160
  if self._read_conn is not None and self._read_conn is not self._conn:
@@ -178,6 +177,34 @@ class SQLiteBackend(StorageBackend):
178
177
 
179
178
  # --- Internal helpers ---
180
179
 
180
+ async def _ensure_wal_mode(self) -> None:
181
+ """Enable WAL once; reapplying it would take a write lock for every CLI read."""
182
+ conn = self._conn_or_raise()
183
+ cursor = await conn.execute("PRAGMA journal_mode")
184
+ row = await cursor.fetchone()
185
+ if row is None or str(row[0]).lower() != "wal":
186
+ await conn.execute("PRAGMA journal_mode=WAL")
187
+
188
+ async def _schema_version(self) -> int:
189
+ cursor = await self._conn_or_raise().execute("PRAGMA user_version")
190
+ row = await cursor.fetchone()
191
+ return int(row[0]) if row is not None else 0
192
+
193
+ async def _initialize_schema(self) -> None:
194
+ """Create or migrate a database once, before marking its schema version."""
195
+ conn = self._conn_or_raise()
196
+ legacy_table = await conn.execute(
197
+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'entities'"
198
+ )
199
+ if await legacy_table.fetchone():
200
+ cursor = await conn.execute("PRAGMA table_info(entities)")
201
+ existing_columns = {row["name"] for row in await cursor.fetchall()}
202
+ if "observed_at" not in existing_columns:
203
+ await conn.execute("ALTER TABLE entities ADD COLUMN observed_at TEXT")
204
+ await conn.executescript(_SCHEMA_SQL)
205
+ await self._run_migrations()
206
+ await conn.execute(f"PRAGMA user_version={_SCHEMA_VERSION}")
207
+
181
208
  async def _run_migrations(self) -> None:
182
209
  conn = self._conn_or_raise()
183
210
  cursor = await conn.execute("PRAGMA table_info(entities)")
@@ -214,6 +241,7 @@ class SQLiteBackend(StorageBackend):
214
241
  or "source_updated_at" not in columns
215
242
  or "retention_policy" not in columns
216
243
  or "retention_parent_id" not in columns
244
+ or await self._retention_policy_needs_migration()
217
245
  )
218
246
  if needs_retention_migration:
219
247
  await self._rebuild_entities_for_retention(columns)
@@ -264,6 +292,14 @@ class SQLiteBackend(StorageBackend):
264
292
  "CREATE INDEX IF NOT EXISTS idx_entities_platform_type_observed_at ON entities(platform, entity_type, observed_at DESC)"
265
293
  )
266
294
 
295
+ async def _retention_policy_needs_migration(self) -> bool:
296
+ """Return whether the entity policy constraint lacks persistent support."""
297
+ cursor = await self._conn_or_raise().execute(
298
+ "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'entities'"
299
+ )
300
+ row = await cursor.fetchone()
301
+ return row is None or "'persistent'" not in str(row["sql"])
302
+
267
303
  async def _rebuild_entities_for_retention(self, columns: set[str]) -> None:
268
304
  """Best-effort migration from source-based timestamps to lifecycle timestamps."""
269
305
  conn = self._conn_or_raise()
@@ -289,7 +325,10 @@ class SQLiteBackend(StorageBackend):
289
325
  source_updated = "updated_at" if "updated_at" in columns else "NULL"
290
326
 
291
327
  if "retention_policy" in columns:
292
- retention_policy = "retention_policy"
328
+ retention_policy = (
329
+ "CASE WHEN platform = 'rss' AND entity_type = 'Folder' THEN 'persistent' "
330
+ "ELSE retention_policy END"
331
+ )
293
332
  else:
294
333
  retention_policy = (
295
334
  "CASE entity_type WHEN 'Person' THEN 'connected' "
@@ -312,11 +351,15 @@ class SQLiteBackend(StorageBackend):
312
351
  "CASE WHEN entity_type IN ('Channel', 'Document', 'Email', 'Folder', 'Spreadsheet') "
313
352
  "AND cumulative_observation_duration_ms > 0 THEN observed_at ELSE NULL END"
314
353
  )
354
+ observed = (
355
+ "CASE WHEN platform = 'rss' AND entity_type = 'Folder' THEN NULL "
356
+ f"ELSE {observed} END"
357
+ )
315
358
  duration = "cumulative_observation_duration_ms" if "cumulative_observation_duration_ms" in columns else "0"
316
359
  bookmarked = "bookmarked" if "bookmarked" in columns else "0"
317
360
  await conn.execute("PRAGMA foreign_keys=OFF")
318
361
  try:
319
- await conn.execute("BEGIN")
362
+ await conn.execute("BEGIN IMMEDIATE")
320
363
  await conn.execute(
321
364
  """
322
365
  CREATE TABLE entities_new (
@@ -335,7 +378,7 @@ class SQLiteBackend(StorageBackend):
335
378
  synced_at TEXT,
336
379
  observed_at TEXT,
337
380
  retention_policy TEXT NOT NULL DEFAULT 'observed'
338
- CHECK (retention_policy IN ('observed', 'owned', 'connected')),
381
+ CHECK (retention_policy IN ('observed', 'owned', 'connected', 'persistent')),
339
382
  retention_parent_id TEXT REFERENCES entities_new(id) ON DELETE CASCADE,
340
383
  cumulative_observation_duration_ms INTEGER NOT NULL DEFAULT 0,
341
384
  bookmarked INTEGER NOT NULL DEFAULT 0,
@@ -436,7 +479,7 @@ class SQLiteBackend(StorageBackend):
436
479
  persons=len(batch.persons),
437
480
  edges=len(batch.edges),
438
481
  ):
439
- await conn.execute("BEGIN")
482
+ await conn.execute("BEGIN IMMEDIATE")
440
483
  try:
441
484
  person_id_map = await self._upsert_persons(
442
485
  conn, batch.persons, person_embeddings
@@ -860,7 +903,7 @@ class SQLiteBackend(StorageBackend):
860
903
  assert self._write_lock is not None
861
904
  async with self._write_lock:
862
905
  conn = self._conn_or_raise()
863
- await conn.execute("BEGIN")
906
+ await conn.execute("BEGIN IMMEDIATE")
864
907
  try:
865
908
  all_ids = [primary_entity_id, *duplicate_ids]
866
909
  placeholders = ",".join("?" * len(all_ids))
@@ -1013,7 +1056,7 @@ class SQLiteBackend(StorageBackend):
1013
1056
  assert self._write_lock is not None
1014
1057
  async with self._write_lock:
1015
1058
  conn = self._conn_or_raise()
1016
- await conn.execute("BEGIN")
1059
+ await conn.execute("BEGIN IMMEDIATE")
1017
1060
  try:
1018
1061
  await conn.execute(
1019
1062
  """
@@ -1583,7 +1626,7 @@ class SQLiteBackend(StorageBackend):
1583
1626
  assert self._write_lock is not None
1584
1627
  async with self._write_lock:
1585
1628
  conn = self._conn_or_raise()
1586
- await conn.execute("BEGIN")
1629
+ await conn.execute("BEGIN IMMEDIATE")
1587
1630
  try:
1588
1631
  before_cursor = await conn.execute("SELECT count(*) FROM entities")
1589
1632
  before_row = await before_cursor.fetchone()
@@ -1706,7 +1749,7 @@ class SQLiteBackend(StorageBackend):
1706
1749
  async with self._write_lock:
1707
1750
  conn = self._conn_or_raise()
1708
1751
  now = _now()
1709
- await conn.execute("BEGIN")
1752
+ await conn.execute("BEGIN IMMEDIATE")
1710
1753
  try:
1711
1754
  cursor = await conn.execute(
1712
1755
  """
@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS entities (
19
19
  synced_at TEXT, -- ISO8601 UTC
20
20
  observed_at TEXT,
21
21
  retention_policy TEXT NOT NULL DEFAULT 'observed'
22
- CHECK (retention_policy IN ('observed', 'owned', 'connected')),
22
+ CHECK (retention_policy IN ('observed', 'owned', 'connected', 'persistent')),
23
23
  retention_parent_id TEXT REFERENCES entities(id) ON DELETE CASCADE,
24
24
  cumulative_observation_duration_ms INTEGER NOT NULL DEFAULT 0,
25
25
  bookmarked INTEGER NOT NULL DEFAULT 0,
@@ -6,7 +6,6 @@ import asyncio
6
6
  import json as _json
7
7
  from collections.abc import Iterator
8
8
  from contextlib import contextmanager
9
- from pathlib import Path
10
9
  from typing import TYPE_CHECKING, cast
11
10
 
12
11
  import typer
@@ -53,19 +52,15 @@ def _status_label(status: str) -> str:
53
52
 
54
53
  @demo_app.command("seed")
55
54
  def seed_demo_command(
56
- config_dir: Path = typer.Option(
57
- ...,
58
- "--config-dir",
59
- help="Explicit non-default config directory for the isolated demo graph",
60
- ),
61
- reset: bool = typer.Option(False, "--reset", help="Replace an existing demo database"),
55
+ force: bool = typer.Option(False, "--force", help="Replace an existing demo database"),
62
56
  json: bool = typer.Option(False, "--json", help="Output as JSON"),
63
57
  ) -> None:
64
58
  """Seed the fictional Atlas graph used by the launch demo."""
59
+ from agentgraph.config import get_config_paths
65
60
  from agentgraph.demo import seed_demo
66
61
 
67
62
  try:
68
- result = asyncio.run(seed_demo(config_dir, reset=reset))
63
+ result = asyncio.run(seed_demo(get_config_paths()[0], force=force))
69
64
  except (FileExistsError, ValueError) as exc:
70
65
  typer.echo(str(exc), err=True)
71
66
  raise typer.Exit(code=1) from exc
@@ -335,12 +330,17 @@ def connector_command(
335
330
  try:
336
331
  result = type(connector).run_cli_command(command_args)
337
332
  effects = type(connector).command_effects(command_args, result)
333
+ if effects.delete_entities:
334
+ from agentgraph.cli_query import run_graph_operation
335
+ from agentgraph.connectors.command_effects import execute_deletions
336
+
337
+ result["deleted_entities"] = run_graph_operation(lambda: execute_deletions(effects))
338
338
  if effects.poll:
339
- from agentgraph.cli_query import queue_connector_poll
339
+ from agentgraph.cli_sync import queue_connector_poll
340
340
 
341
341
  result["poll"] = queue_connector_poll(connector.source)
342
342
  if effects.ingest:
343
- from agentgraph.cli_query import queue_connector_ingest
343
+ from agentgraph.cli_sync import queue_connector_ingest
344
344
 
345
345
  result["ingest"] = queue_connector_ingest(
346
346
  connector.source,
@@ -644,8 +644,17 @@ def mcp_config() -> None:
644
644
 
645
645
  @app.command()
646
646
  def install_skill(
647
- skill: str = typer.Argument("graph", help="Bundled skill to install"),
648
- target: str = typer.Option("user", "--target", help="Install target: user or project"),
647
+ skill: str = typer.Argument("AgentGraph", help="Bundled skill to install"),
648
+ target: str = typer.Option(
649
+ "user",
650
+ "--target",
651
+ help="Install target: user (~/.agents/skills) or project (./.agents/skills)",
652
+ ),
653
+ claude: bool = typer.Option(
654
+ True,
655
+ "--claude/--no-claude",
656
+ help="Link into Claude by default; --no-claude skips ~/.claude/skills or ./.claude/skills",
657
+ ),
649
658
  force: bool = typer.Option(False, "--force", help="Overwrite an existing installed skill"),
650
659
  json: bool = typer.Option(False, "--json", help="Output as JSON"),
651
660
  ) -> None:
@@ -658,7 +667,7 @@ def install_skill(
658
667
  raise typer.Exit(code=1)
659
668
 
660
669
  try:
661
- result = install_agentgraph_skill(skill, target=target, force=force)
670
+ result = install_agentgraph_skill(skill, target=target, force=force, claude=claude)
662
671
  except SkillInstallError as exc:
663
672
  typer.echo(str(exc), err=True)
664
673
  raise typer.Exit(code=1) from exc
@@ -668,6 +677,8 @@ def install_skill(
668
677
  return
669
678
 
670
679
  typer.echo(f"Installed AgentGraph skill '{result.skill}' to {result.destination}")
680
+ if result.claude_destination is not None:
681
+ typer.echo(f"Linked Claude skill to {result.claude_destination}")
671
682
 
672
683
 
673
684
  @app.command()
@@ -710,7 +721,7 @@ def poll(
710
721
  json: bool = typer.Option(False, "--json", help="Output as JSON"),
711
722
  ) -> None:
712
723
  """Trigger a background poll for one or all connectors."""
713
- from agentgraph.cli_query import cmd_poll
724
+ from agentgraph.cli_sync import cmd_poll
714
725
 
715
726
  cmd_poll(source=source, as_json=json)
716
727