Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,8 @@ jobs:
matrix:
module:
- hook-context-intelligence
- tool-blob-read
- tool-context-intelligence-query
- tool-context-intelligence-upload
- tool-graph-query
steps:
- uses: actions/checkout@v4

Expand All @@ -90,10 +89,18 @@ jobs:
- name: Install dependencies
working-directory: modules/${{ matrix.module }}
# --frozen: respect the module's own lockfile exactly as committed.
# The module's path dependency on `../..` is resolved from the
# checked-out root, so no special setup is needed.
# The module declares the shared bundle as a @main git ref (NOT a path
# source), so uv installs the *published* context_intelligence. The Run
# tests step shadows it with the in-repo source (below).
run: uv sync --frozen

- name: Run tests
working-directory: modules/${{ matrix.module }}
# Test the module against the IN-REPO shared library, which may carry
# not-yet-published changes (e.g. context_intelligence/tool_resolver.py)
# that @main only gains when this PR merges. Put the checked-out root on
# PYTHONPATH to shadow the @main-installed copy — the same convention
# AGENTS.md prescribes for local runs.
env:
PYTHONPATH: ${{ github.workspace }}
run: uv run pytest tests/ -q --tb=short --ignore=tests/dtu
167 changes: 85 additions & 82 deletions README.md

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions agents/graph-analyst.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ model_role: [reasoning, general]
tools:
- module: tool-delegate
source: git+https://github.com/microsoft/amplifier-foundation@main#subdirectory=modules/tool-delegate
- module: tool-graph-query
source: git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main#subdirectory=modules/tool-graph-query
- module: tool-blob-read
source: git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main#subdirectory=modules/tool-blob-read
- module: tool-context-intelligence-query
source: git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main#subdirectory=modules/tool-context-intelligence-query
- module: tool-filesystem
source: git+https://github.com/microsoft/amplifier-module-tool-filesystem@main
config:
Expand Down
34 changes: 17 additions & 17 deletions bundle.dot
Original file line number Diff line number Diff line change
@@ -1,55 +1,55 @@
// This repository packages tools and AI assistants that record, search, and analyze Amplifier's own work-session history.
digraph context_intelligence {
rankdir=LR
fontname="Helvetica"
fontsize=12
label="context-intelligence v0.1.0 — bundle repo"
label="Context IntelligenceSession History Toolkit\nA bundle that records and analyzes Amplifier's past work sessions (v0.1.0)"
labelloc=t
labeljust=c
nodesep=0.6
ranksep=0.7
bgcolor="white"
source_hash="dba7ae999a5fd095337bcd9ede4c54b866044c0b31d63d20c4b0ef4483abaf6c"
source_hash="4636e62f1ae10ff3f60e2b22dcb755c49a7851ad51669ebb1fcb900c54a33559"

node [fontname="Helvetica", fontsize=11, style="filled,rounded"]
edge [fontname="Helvetica", fontsize=9]

root_context_intelligence [label="context-intelligence v0.1.0\n0 tools · 0 agents\n~98 tok aggregate", shape=box, fillcolor="#80cbc4", style="filled,rounded,bold", penwidth=2]
root_context_intelligence [label="Context Intelligence (Main Package)\n0 tools · 0 agents\n~98 tok aggregate", shape=box, fillcolor="#80cbc4", style="filled,rounded,bold", penwidth=2]

subgraph cluster_behaviors {
label="Behaviors"
label="Capability Bundles"
style="filled"
fillcolor="#f9f9f9"
color="#999999"

beh_context_intelligence_behavior [label="context-intelligence-behavior\n4 tools\n~1832 tok", shape=box, fillcolor="#e0f2f1", style="filled,rounded"]
beh_context_intelligence_behavior [label="Session Analysis Toolkit\n4 tools\n~2549 tok", shape=box, fillcolor="#e0f2f1", style="filled,rounded"]
}

subgraph cluster_agents {
label="Agents"
label="Specialist Assistants"
style="filled"
fillcolor="#f9f9f9"
color="#999999"

agt_context_intelligence_design_facilitator [label="context-intelligence-design-facilitator\n~187 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_context_intelligence_tool_designer [label="context-intelligence-tool-designer\n~198 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_graph_analyst [label="graph-analyst\n~543 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_session_navigator [label="session-navigator\n~366 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_context_intelligence_design_facilitator [label="Design Helper\n(guides new features)\n~187 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_context_intelligence_tool_designer [label="Tool Builder Helper\n~198 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_graph_analyst [label="Session Graph Analyst\n(answers questions about sessions)\n~543 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
agt_session_navigator [label="Session File Navigator\n(reads raw session files)\n~366 tok desc", shape=box, fillcolor="#c8e6c9", style="filled,rounded"]
}

subgraph cluster_modules {
label="Modules"
label="Building Blocks (Code Components)"
style="filled"
fillcolor="#f9f9f9"
color="#999999"

mod_hook_context_intelligence [label="hook-context-intelligence", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
mod_tool_blob_read [label="tool-blob-read", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
mod_tool_context_intelligence_upload [label="tool-context-intelligence-upload", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
mod_tool_graph_query [label="tool-graph-query", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
mod_hook_context_intelligence [label="Auto Session Recorder", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
mod_tool_context_intelligence_query [label="Session Search Tool", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
mod_tool_context_intelligence_upload [label="Session Upload Tool", shape=box, fillcolor="#bbdefb", style="filled,rounded"]
}

subgraph cluster_legend {
label="Legend"
label="Legend (Color Key)"
style="filled"
fillcolor="white"
color="#cccccc"
Expand All @@ -69,7 +69,7 @@ digraph context_intelligence {

disclaimer [label="Token estimates: ~4 chars/token\nSolid border = local (counted)\nDashed + red = external, hidden cost (not counted)\nDashed + muted = external, no cost\nExcludes: sub-session costs, runtime-dynamic", shape=note, fillcolor="#eceff1", style="filled", fontsize=9]

ext_githttps___github_com_microsoft_amplifier_foundation_main [label="amplifier-foundation\n(external, cost)", shape=box, fillcolor="#80cbc4", style="dashed", color="red", penwidth=2]
ext_githttps___github_com_microsoft_amplifier_foundation_main [label="amplifier-foundation\n(shared base, external)", shape=box, fillcolor="#80cbc4", style="dashed", color="red", penwidth=2]

root_context_intelligence -> ext_githttps___github_com_microsoft_amplifier_foundation_main [style=dashed]
root_context_intelligence -> beh_context_intelligence_behavior [label="composes"]
Expand Down
Binary file modified bundle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 72 additions & 21 deletions context/config-resolution.dot
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
// ConfigResolver — lazy fallback chain for hook configuration values.
// Config resolution — two resolvers, lazy fallback chains.
//
// Each property is resolved on first access and cached.
// Empty strings in config are treated as absent and fall through
// to the next source in the chain (standard or-chain falsy semantics).
// HookConfigResolver (write/upload side) — owns workspace, paths, and the
// `destinations` fan-out map.
// ToolConfigResolver (read/query side) — used by the graph_query and
// blob_read tools; resolves the query endpoint via
// resolve_query_endpoint(), EXPLICIT-READ-CONFIG FIRST,
// then falling back to the first hook destination, then env.
//
// Each value is resolved on first access and cached. Empty strings are treated
// as absent and fall through to the next source (or-chain falsy semantics).
//
// Render: dot -Tsvg config-resolution.dot -o config-resolution.svg

digraph ConfigResolver {
digraph ConfigResolution {
rankdir=TB;
fontname="Helvetica";
node [fontname="Helvetica", shape=box, style="rounded,filled", fillcolor="#f0f0f0"];
edge [fontname="Helvetica", fontsize=10];

// --- Central node ---
resolver [label="ConfigResolver", shape=component, fillcolor="#d4e6f1", style="filled"];
// --- Central nodes ---
hook_resolver [label="HookConfigResolver\n(hook / write side)", shape=component, fillcolor="#d4e6f1", style="filled"];
tool_resolver [label="ToolConfigResolver\n(tool-context-intelligence-query\ngraph_query + blob_read — read side)", shape=component, fillcolor="#d1f2d4", style="filled"];

// --- Input sources ---
subgraph cluster_inputs {
Expand All @@ -22,14 +29,16 @@ digraph ConfigResolver {
color="#999999";

hook_config [label="hook config\n(behavior YAML)"];
tool_config [label="tool config\n(overrides.tool-context-intelligence-query.config)"];
coord_config [label="coordinator.config\n(set by app at session creation)"];
session_cap [label="session.working_dir\ncapability\n(set by foundation bundle.py)"];
env_vars [label="Environment Variables\nAMPLIFIER_CONTEXT_INTELLIGENCE_*\n(expanded in YAML before mount)", shape=note, fillcolor="#fff3cd"];
}

env_vars -> hook_config [label="expanded into", style=dashed];
env_vars -> tool_config [label="expanded into", style=dashed];

// --- project_slug resolution ---
// --- project_slug resolution (hook) ---
subgraph cluster_project_slug {
label="project_slug resolution";
style=filled;
Expand All @@ -46,7 +55,7 @@ digraph ConfigResolver {
ps_working_dir -> ps_default [label="empty?"];
}

// --- base_path resolution ---
// --- base_path resolution (hook) ---
subgraph cluster_base_path {
label="base_path resolution";
style=filled;
Expand All @@ -61,7 +70,7 @@ digraph ConfigResolver {
bp_coord -> bp_default [label="empty?"];
}

// --- workspace resolution ---
// --- workspace resolution (hook) ---
subgraph cluster_workspace {
label="workspace resolution";
style=filled;
Expand All @@ -76,19 +85,35 @@ digraph ConfigResolver {
ws_config -> ws_slug [label="empty?"];
}

// --- Simple properties (no fallback chain) ---
// --- destinations: the hook's named server map (write / fan-out) ---
subgraph cluster_destinations {
label="destinations (hook write side — fan-out)";
style=filled;
fillcolor="#ede7f6";
color="#673ab7";

dst_map [label="destinations:\n{ name → Destination(url, api_key,\n include, exclude) }\nnamed map, declaration order"];
dst_legacy [label="legacy scalars\ncontext_intelligence_server_url / _api_key\n→ synthesize {\"default\": …}\nONLY when no destinations block\n(both fields required)", shape=note, fillcolor="#fff3cd"];
dst_fanout [label="fan-out: send to EVERY destination where\ninclude matches AND exclude does not\n(exclude wins; zero / one / several)\nlocal JSONL always written"];
dst_first [label="first destination\n= next(iter(destinations.values()), None)"];

dst_legacy -> dst_map [label="synthesize", style=dashed];
dst_map -> dst_fanout [label="route"];
dst_map -> dst_first [label="for queries"];
}

// --- Simple hook properties (no coordinator fallback) ---
subgraph cluster_simple {
label="Direct config properties\n(no coordinator fallback)";
label="Direct hook config properties";
style=filled;
fillcolor="#f3e5f5";
color="#9c27b0";

server_url [label="context_intelligence_server_url\nNone if empty"];
log_level [label="log_level\ndefault: INFO"];
exclude [label="exclude_events\ndefault: frozenset()"];
}

// --- Derived paths ---
// --- Derived paths (hook) ---
subgraph cluster_derived {
label="Derived Paths";
style=filled;
Expand All @@ -99,16 +124,42 @@ digraph ConfigResolver {
blob_root [label="blob_store_root\nbase_path / project_slug /\nsessions"];
}

// --- Edges from resolver ---
resolver -> ps_config [lhead=cluster_project_slug];
resolver -> bp_config [lhead=cluster_base_path];
resolver -> ws_coord [lhead=cluster_workspace];
resolver -> server_url [lhead=cluster_simple];
// --- Query endpoint resolution (read side, ToolConfigResolver) ---
// resolve_query_endpoint(hook_resolver, tool_resolver): per-field, EXPLICIT-FIRST.
subgraph cluster_query {
label="resolve_query_endpoint() (read side — per field, explicit-first)";
style=filled;
fillcolor="#e0f2f1";
color="#009688";

q1 [label="1. sources[first]\n.url / .api_key\n(explicit read override;\n absent ⇒ synthesize default from\n tool's explicit scalars)"];
q2 [label="2. hook destinations[first]\n.url / .api_key\n(bug-fix bridge: destinations-only\n setups 'just work' for reads)"];
q3 [label="3. env\nAMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_URL /\n_API_KEY\n(single canonical last-resort fallback)"];
q_none [label="else → None\n→ configuration_error", shape=note, fillcolor="#ffcdd2"];

q1 -> q2 [label="empty?"];
q2 -> q3 [label="empty?"];
q3 -> q_none [label="empty?"];
}

// --- Edges from the hook resolver ---
hook_resolver -> ps_config [lhead=cluster_project_slug];
hook_resolver -> bp_config [lhead=cluster_base_path];
hook_resolver -> ws_coord [lhead=cluster_workspace];
hook_resolver -> dst_map [lhead=cluster_destinations];
hook_resolver -> log_level [lhead=cluster_simple];

hook_config -> resolver [label="config dict"];
coord_config -> resolver [label="coordinator"];
hook_config -> hook_resolver [label="config dict"];
coord_config -> hook_resolver [label="coordinator"];
session_cap -> ps_working_dir [label="working_dir", style=dashed];

// --- Edges from the tool resolver ---
tool_resolver -> q1 [lhead=cluster_query];
tool_config -> tool_resolver [label="config dict"];
coord_config -> tool_resolver [label="coordinator"];
// tier 2 reads the hook's first destination (shared model)
dst_first -> q2 [style=dashed, label="first hook destination", color="#673ab7"];

// --- Derived path dependencies ---
ps_default -> session_dir [style=dashed, label="project_slug", color="#999999"];
bp_default -> session_dir [style=dashed, label="base_path", color="#999999"];
Expand Down
49 changes: 49 additions & 0 deletions context_intelligence/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import logging
import os
import re
from pathlib import Path

log = logging.getLogger("context_intelligence.config")
Expand All @@ -29,6 +30,54 @@
SETTINGS_PATH = AMPLIFIER_DIR / "settings.yaml"


# ---------------------------------------------------------------------------
# Shared env-var helpers (used by HookConfigResolver and ToolConfigResolver)
# ---------------------------------------------------------------------------

#: Environment variable prefix shared by all CI configuration.
#: ``AMPLIFIER_CONTEXT_INTELLIGENCE_WORKSPACE`` → workspace
#: ``AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_URL`` → context_intelligence_server_url
#: etc.
_ENV_PREFIX = "AMPLIFIER_CONTEXT_INTELLIGENCE_"


def _env(suffix: str) -> str | None:
"""Read ``AMPLIFIER_CONTEXT_INTELLIGENCE_<SUFFIX>`` from the environment.

Returns the value as a string if set and non-empty, otherwise ``None``.
"""
value = os.environ.get(_ENV_PREFIX + suffix)
return value if value else None


# ---------------------------------------------------------------------------
# Shell-style placeholder expander (used by ToolConfigResolver)
# ---------------------------------------------------------------------------

_PLACEHOLDER_RE = re.compile(r"\$\{([^}:]+)(?::([^}]*))?\\}")


def _expand_env_placeholders(value: str) -> str:
"""Expand shell-style ``${VAR}``, ``${VAR:}``, ``${VAR:default}`` placeholders.

- ``${VAR}`` — replaced with ``os.environ[VAR]`` if set, else ``""``.
- ``${VAR:}`` — same as ``${VAR}`` (empty default when var is unset).
- ``${VAR:default}`` — replaced with ``os.environ[VAR]`` if set, else ``"default"``.
- Non-placeholder strings pass through unchanged.

Note: ``os.path.expandvars`` does **not** support the ``${VAR:default}``
colon syntax used by the agent behavior YAML files shipped with this bundle,
hence this small regex-based helper.
"""

def _replace(m: re.Match[str]) -> str:
var_name = m.group(1)
default = m.group(2) if m.group(2) is not None else ""
return os.environ.get(var_name, default)

return _PLACEHOLDER_RE.sub(_replace, value)


# ---------------------------------------------------------------------------
# Settings.yaml parser
# ---------------------------------------------------------------------------
Expand Down
Loading
Loading